Add list of allowable experimental notations
Upstream CL aosp/2081700 surfaced up allowed experimental annotations.
Bug: 222554358
Test: presubmit
Change-Id: I2199b68f56c91f35191a9f8172b2e6a198e58daa
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt b/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
index b484ffc..3a09d59 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
@@ -197,14 +197,24 @@
val isUsedInSameArtifact = usageCoordinates.artifactId == annotationCoordinates.artifactId
val isAtomic = atomicGroupList.contains(usageGroupId)
+ val annotationQualifiedName = annotation.getQualifiedName()
+ val isAnnotationAllowed = if (annotationQualifiedName != null) {
+ isAnnotationAlwaysAllowed(annotationQualifiedName)
+ } else {
+ false
+ }
+
/**
* Usage of experimental APIs is allowed in either of the following conditions:
*
* - Both the group ID and artifact ID in `usageCoordinates` and
* `annotationCoordinates` match
* - The group IDs match, and that group ID is atomic
+ * - The annotation being used is is an allowlist
*/
- if ((isUsedInSameGroup && isUsedInSameArtifact) || (isUsedInSameGroup && isAtomic)) return
+ if ((isUsedInSameGroup && isUsedInSameArtifact) ||
+ (isUsedInSameGroup && isAtomic) ||
+ isAnnotationAllowed) return
// Log inappropriate experimental usage
if (DEBUG) {
@@ -233,7 +243,12 @@
return coord
}
}
- return getLibrary(File(findJarPath(element)))
+ val findJarPath = findJarPath(element)
+ return if (findJarPath != null) {
+ getLibrary(File(findJarPath))
+ } else {
+ null
+ }
}
private fun UElement.getQualifiedName() = (this as UClass).qualifiedName
@@ -282,5 +297,19 @@
Scope.JAVA_FILE_SCOPE,
),
)
+
+ /**
+ * Checks to see if the given annotation is always allowed for use in @OptIn.
+ * For now, allow Kotlin all stdlib experimental annotations.
+ */
+ internal fun isAnnotationAlwaysAllowed(annotation: String): Boolean {
+ val allowedExperimentalAnnotations = listOf(
+ Regex("kotlin\\..*"),
+ Regex("kotlinx\\..*"),
+ )
+ return allowedExperimentalAnnotations.any {
+ annotation.matches(it)
+ }
+ }
}
}
diff --git a/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt b/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
index 5512fd3..129087f 100644
--- a/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
@@ -18,6 +18,7 @@
package androidx.build.lint
+import androidx.build.lint.BanInappropriateExperimentalUsage.Companion.isAnnotationAlwaysAllowed
import com.android.tools.lint.checks.infrastructure.ProjectDescription
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestMode
@@ -44,6 +45,20 @@
) {
@Test
+ fun `Check if annotation is always allowed`() {
+
+ // List of Kotlin stdlib experimental annotations used in AndroidX
+ assertTrue(isAnnotationAlwaysAllowed("kotlin.contracts.ExperimentalContracts"))
+ assertTrue(isAnnotationAlwaysAllowed("kotlin.ExperimentalStdlibApi"))
+ assertTrue(isAnnotationAlwaysAllowed("kotlin.experimental.ExperimentalTypeInference"))
+ assertTrue(isAnnotationAlwaysAllowed("kotlinx.coroutines.DelicateCoroutinesApi"))
+ assertTrue(isAnnotationAlwaysAllowed("kotlinx.coroutines.ExperimentalCoroutinesApi"))
+
+ assertFalse(isAnnotationAlwaysAllowed("androidx.foo.bar"))
+ assertFalse(isAnnotationAlwaysAllowed("com.google.foo.bar"))
+ }
+
+ @Test
fun `Test same atomic module Experimental usage via Gradle model`() {
val provider = project()
.name("provider")