Snap for 6724178 from 2a0762bffc9fa7711fa5805e7c2ad6b8339c62d3 to sc-d1-release
Change-Id: Ic995e0e0b7fa09125bf2623d28b89736e3ae57c6
diff --git a/Android.bp b/Android.bp
index 7df7356..4db5e7f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -60,3 +60,29 @@
path: "src/test/res",
srcs: ["src/test/res/**/*.java"],
}
+
+// ------------------------- AndroidLint Checkers ----------------------------------
+
+java_library_host {
+ name: "JavaKotlinApiFinder",
+ srcs: ["checks/src/main/java/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+}
+
+// TODO: (b/162368644) Implement these (working in gradle) Kotlin Tests to run on Soong
+//java_test_host {
+// name: "JavaKotlinApiFinderTest",
+// srcs: [
+// "checks/src/test/java/**/*.kt",
+// "checks/src/main/java/**/*.kt",
+// ],
+// plugins: ["auto_service_plugin"],
+// static_libs: [
+// "auto_service_annotations",
+// "lint_api",
+// ],
+//}
diff --git a/checks/src/main/java/com/android/apifinder/ApiFinderDetector.kt b/checks/src/main/java/com/android/apifinder/ApiFinderDetector.kt
new file mode 100644
index 0000000..1c72a83
--- /dev/null
+++ b/checks/src/main/java/com/android/apifinder/ApiFinderDetector.kt
@@ -0,0 +1,98 @@
+package com.android.apifinder
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.*
+import com.intellij.lang.jvm.JvmModifier
+import com.intellij.psi.*
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.USimpleNameReferenceExpression
+
+@Suppress("UnstableApiUsage")
+class ApiFinderDetector : Detector(), Detector.UastScanner {
+
+ override fun getApplicableUastTypes(): List<Class<out UElement>>? =
+ listOf(UCallExpression::class.java, USimpleNameReferenceExpression::class.java,
+ UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? =
+ object : UElementHandler() {
+ // Visits all methods that the module itself has defined in the source code.
+ override fun visitMethod(node: UMethod) {
+ val method = node.sourcePsi as? PsiMethod ?: return
+ visitGenericMethod(method, node, isModuleMethod = true)
+ }
+
+ // Visits all method call expressions in the source code.
+ override fun visitCallExpression(node: UCallExpression) {
+ val method = node.resolve() ?: return
+ visitGenericMethod(method, node)
+ }
+
+ // When Kotlin code refers to a Java `getFoo()` or `setFoo()` method with property syntax
+ // * (`obj.foo`), "foo" is represented by a [USimpleNameReferenceExpression].
+ // This ensures that we visit these method calls as well.
+ override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression) {
+ val method = node.resolve() as? PsiMethod ?: return
+ visitGenericMethod(method, node)
+ }
+
+ private fun visitGenericMethod(
+ method: PsiMethod, node: UElement, isModuleMethod: Boolean = false
+ ) {
+ // Exclude non-public calls.
+ if (!method.hasModifier(JvmModifier.PUBLIC)) {
+ return
+ }
+ var containingClass = method.containingClass
+ while (containingClass != null) {
+ if (!containingClass.hasModifier(JvmModifier.PUBLIC)) {
+ return
+ }
+ containingClass = containingClass.containingClass
+ }
+
+ // Construct message as enclosingClassName.className.methodName(parameterNames).
+ // e.g. <com.android.server.wifi>.<WifiNetworkFactory>.<hasConnectionRequests>()
+ val className = method.containingClass!!.qualifiedName!!
+ val methodName = if (method.isConstructor) {
+ val containingClasses = mutableListOf<PsiClass>()
+ containingClass = method.containingClass
+ while (containingClass != null) {
+ containingClasses += containingClass
+ containingClass = containingClass.containingClass
+ }
+ containingClasses.asReversed().joinToString(".") { it.name!! }
+ } else {
+ method.name
+ }
+ val parameterNames = method.parameterList.parameters.map {
+ it.type.canonicalText.replace(Regex("<.*>"), "")
+ }.joinToString(", ")
+
+ val methodCall = "$className.$methodName($parameterNames)"
+ if (isModuleMethod) {
+ val message = "ModuleMethod:" + methodCall
+ context.report(ISSUE, node, context.getLocation(node), message)
+ } else {
+ val message = "Method:" + methodCall
+ context.report(ISSUE, context.getLocation(node), message)
+ }
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE = Issue.create(
+ id = "JavaKotlinApiUsedByModule",
+ briefDescription = "API used by Android module",
+ explanation = "Any public/protected items used by an Android module.",
+ category = Category.TESTING,
+ priority = 6,
+ severity = Severity.INFORMATIONAL,
+ implementation = Implementation(ApiFinderDetector::class.java, Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
+
diff --git a/checks/src/main/java/com/android/apifinder/ApiFinderIssueRegistry.kt b/checks/src/main/java/com/android/apifinder/ApiFinderIssueRegistry.kt
new file mode 100644
index 0000000..f2f91e2
--- /dev/null
+++ b/checks/src/main/java/com/android/apifinder/ApiFinderIssueRegistry.kt
@@ -0,0 +1,12 @@
+package com.android.apifinder
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class ApiFinderIssueRegistry : IssueRegistry() {
+ override val api: Int
+ get() = 7
+ override val issues = listOf(ApiFinderDetector.ISSUE)
+}
diff --git a/checks/src/test/java/com/android/apifinder/ApiFinderDetectorTest.kt b/checks/src/test/java/com/android/apifinder/ApiFinderDetectorTest.kt
new file mode 100644
index 0000000..397fe07
--- /dev/null
+++ b/checks/src/test/java/com/android/apifinder/ApiFinderDetectorTest.kt
@@ -0,0 +1,104 @@
+package com.android.apifinder
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class ApiFinderDetectorTest : LintDetectorTest() {
+ fun testJava() {
+ lint()
+ .files(
+ java(
+// TODO: Remove the explicit constructors once UCallExpression.resolve() can resolve generated
+// default constructors in Java.
+ """
+package com.android.apifinder;
+
+public class TestClass {
+ public class PublicSubclass {
+ public PublicSubclass() {}
+ public void publicMethod() {}
+ private void privateMethod() {}
+ }
+
+ private class PrivateSubclass {
+ public PrivateSubclass() {}
+ public void publicMethod() {}
+ }
+
+ public void testMethod() {
+ PublicSubclass publicSubclass = new PublicSubclass();
+ publicSubclass.publicMethod();
+ publicSubclass.privateMethod();
+ PrivateSubclass privateSubclass = new PrivateSubclass();
+ privateSubclass.publicMethod();
+ }
+}
+ """
+ ).indented()
+ )
+ .run()
+ .expect(
+ """
+src/com/android/apifinder/TestClass.java:16: Information: ModuleMethod:com.android.apifinder.TestClass.PublicSubclass.TestClass.PublicSubclass() [JavaKotlinApiUsedByModule]
+ PublicSubclass publicSubclass = new PublicSubclass();
+ ~~~~~~~~~~~~~~~~~~~~
+src/com/android/apifinder/TestClass.java:17: Information: ModuleMethod:com.android.apifinder.TestClass.PublicSubclass.publicMethod() [JavaKotlinApiUsedByModule]
+ publicSubclass.publicMethod();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 2 warnings
+ """
+ )
+ }
+
+ fun testKotlin() {
+ lint()
+ .files(
+ kotlin(
+ """
+package com.android.apifinder
+
+class TestClass {
+ class PublicSubclass {
+ fun publicMethod() {}
+ private fun privateMethod() {}
+ }
+
+ private class PrivateSubclass {
+ fun publicMethod() {}
+ }
+
+ fun testMethod() {
+ val publicSubclass = PublicSubclass()
+ publicSubclass.publicMethod()
+ publicSubclass.privateMethod()
+ val privateSubclass = PrivateSubclass()
+ privateSubclass.publicMethod()
+ }
+}
+ """
+ ).indented()
+ )
+ .run()
+ .expect(
+ """
+src/com/android/apifinder/TestClass.kt:14: Information: ModuleMethod:com.android.apifinder.TestClass.PublicSubclass.TestClass.PublicSubclass() [JavaKotlinApiUsedByModule]
+ val publicSubclass = PublicSubclass()
+ ~~~~~~~~~~~~~~~~
+src/com/android/apifinder/TestClass.kt:15: Warning: ModuleMethod:com.android.apifinder.TestClass.PublicSubclass.publicMethod() [JavaKotlinApiUsedByModule]
+ publicSubclass.publicMethod()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 2 warnings
+ """
+ )
+ }
+
+ override fun getDetector(): Detector {
+ return ApiFinderDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(ApiFinderDetector.ISSUE)
+ }
+}