Lint check for usages of JetBrains nullability annotations in Java files.

Test: Unit tests of the annotations in Java and Kotlin code.
Bug: 221275214
Change-Id: I8f0df1c9878bebeece54f58697409b25788172ba
diff --git a/lint-checks/integration-tests/src/main/java/androidx/NullabilityAnnotationsJava.java b/lint-checks/integration-tests/src/main/java/androidx/NullabilityAnnotationsJava.java
new file mode 100644
index 0000000..4f32ad8
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/androidx/NullabilityAnnotationsJava.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sample;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Sample class for testing JetBrains nullability annotations.
+ */
+@SuppressWarnings("unused")
+public class NullabilityAnnotationsJava {
+    /**
+     * Sample method
+     * @param arg NotNull arg
+     */
+    private void method1(@NotNull String arg) {
+    }
+
+    /**
+     * Sample method
+     * @param arg Nullable arg
+     */
+    private void method2(@Nullable String arg) {
+    }
+}
diff --git a/lint-checks/integration-tests/src/main/java/androidx/NullabilityAnnotationsKotlin.kt b/lint-checks/integration-tests/src/main/java/androidx/NullabilityAnnotationsKotlin.kt
new file mode 100644
index 0000000..3f57835
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/androidx/NullabilityAnnotationsKotlin.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sample
+
+import org.jetbrains.annotations.NotNull
+import org.jetbrains.annotations.Nullable
+
+@Suppress("unused", "UNUSED_PARAMETER")
+class NullabilityAnnotationsKotlin {
+    private fun method1(@NotNull arg: String) {}
+
+    private fun method2(@Nullable arg: String?) {}
+}
diff --git a/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt b/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
new file mode 100644
index 0000000..6ed71ee
--- /dev/null
+++ b/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2022 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.isJava
+import org.jetbrains.uast.UAnnotation
+
+/**
+ * Checks for usages of JetBrains nullability annotations in Java code.
+ */
+class NullabilityAnnotationsDetector : Detector(), Detector.UastScanner {
+    override fun getApplicableUastTypes() = listOf(UAnnotation::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler {
+        return AnnotationChecker(context)
+    }
+
+    private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
+        override fun visitAnnotation(node: UAnnotation) {
+            if (isJava(node.sourcePsi)) {
+                checkForAnnotation(node, "NotNull", "NonNull")
+                checkForAnnotation(node, "Nullable", "Nullable")
+            }
+        }
+
+        /**
+         * Check if the node is org.jetbrains.annotations.$jetBrainsAnnotation, replace with
+         * androidx.annotation.$androidxAnnotation if so.
+         */
+        private fun checkForAnnotation(
+            node: UAnnotation,
+            jetBrainsAnnotation: String,
+            androidxAnnotation: String
+        ) {
+            val incorrectAnnotation = "org.jetbrains.annotations.$jetBrainsAnnotation"
+            val replacementAnnotation = "androidx.annotation.$androidxAnnotation"
+            val patternToReplace = "(?:org\\.jetbrains\\.annotations\\.)?$jetBrainsAnnotation"
+
+            if (node.qualifiedName == incorrectAnnotation) {
+                val lintFix = fix().name("Replace with `@$replacementAnnotation`")
+                    .replace()
+                    .pattern(patternToReplace)
+                    .with(replacementAnnotation)
+                    .shortenNames()
+                    .autoFix(true, true)
+                    .build()
+                val incident = Incident(context)
+                    .issue(ISSUE)
+                    .fix(lintFix)
+                    .location(context.getNameLocation(node))
+                    .message("Use `@$replacementAnnotation` instead of `@$incorrectAnnotation`")
+                    .scope(node)
+                context.report(incident)
+            }
+        }
+    }
+
+    companion object {
+        val ISSUE = Issue.create(
+            "NullabilityAnnotationsDetector",
+            "Replace usages of JetBrains nullability annotations with androidx " +
+                "versions in Java code",
+            "The androidx nullability annotations should be used in androidx libraries " +
+                "instead of JetBrains annotations.",
+            Category.CORRECTNESS, 5, Severity.ERROR,
+            Implementation(NullabilityAnnotationsDetector::class.java, Scope.JAVA_FILE_SCOPE)
+        )
+    }
+}
diff --git a/lint-checks/src/test/java/androidx/build/lint/NullabilityAnnotationsDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/NullabilityAnnotationsDetectorTest.kt
new file mode 100644
index 0000000..c9c423c
--- /dev/null
+++ b/lint-checks/src/test/java/androidx/build/lint/NullabilityAnnotationsDetectorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2022 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import androidx.build.lint.Stubs.Companion.JetBrainsAnnotations
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class NullabilityAnnotationsDetectorTest : AbstractLintDetectorTest(
+    useDetector = NullabilityAnnotationsDetector(),
+    useIssues = listOf(NullabilityAnnotationsDetector.ISSUE),
+    ) {
+    @Test
+    fun `Detection of Jetbrains nullability usage in Java sources`() {
+        val input = arrayOf(
+            javaSample("androidx.NullabilityAnnotationsJava"),
+            JetBrainsAnnotations
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/androidx/sample/NullabilityAnnotationsJava.java:31: Error: Use @androidx.annotation.NonNull instead of @org.jetbrains.annotations.NotNull [NullabilityAnnotationsDetector]
+    private void method1(@NotNull String arg) {
+                         ~~~~~~~~
+src/androidx/sample/NullabilityAnnotationsJava.java:38: Error: Use @androidx.annotation.Nullable instead of @org.jetbrains.annotations.Nullable [NullabilityAnnotationsDetector]
+    private void method2(@Nullable String arg) {
+                         ~~~~~~~~~
+2 errors, 0 warnings
+    """.trimIndent()
+
+        val expectFixDiffs = """
+Autofix for src/androidx/sample/NullabilityAnnotationsJava.java line 31: Replace with `@androidx.annotation.NonNull`:
+@@ -31 +31
+-     private void method1(@NotNull String arg) {
++     private void method1(@androidx.annotation.NonNull String arg) {
+Autofix for src/androidx/sample/NullabilityAnnotationsJava.java line 38: Replace with `@androidx.annotation.Nullable`:
+@@ -38 +38
+-     private void method2(@Nullable String arg) {
++     private void method2(@androidx.annotation.Nullable String arg) {
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input)
+            .expect(expected)
+            .expectFixDiffs(expectFixDiffs)
+    }
+
+    @Test
+    fun `JetBrains annotations allowed in Kotlin sources`() {
+        val input = arrayOf(
+            ktSample("androidx.NullabilityAnnotationsKotlin"),
+            JetBrainsAnnotations
+        )
+        check(*input).expectClean()
+    }
+}
diff --git a/lint-checks/src/test/java/androidx/build/lint/Stubs.kt b/lint-checks/src/test/java/androidx/build/lint/Stubs.kt
index 4db14ac..06ae21f 100644
--- a/lint-checks/src/test/java/androidx/build/lint/Stubs.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/Stubs.kt
@@ -245,6 +245,14 @@
             """
         )
 
+        val JetBrainsAnnotations = TestFiles.kotlin(
+            """
+package org.jetbrains.annotations
+
+annotation class NotNull
+annotation class Nullable
+            """
+        )
         /* ktlint-enable max-line-length */
     }
 }