Add a linter requiring Nullability Annotations
-This linter only check Java source and its public methods.
-Apply to method return type not primitives or void.
-Apply to method parameters not primitives.
-Check the android.annotation.NonNull / android.annotation.Nullable
to replace with androidx.annotation.NonNull / androidx.annotation.Nullable.
Bug: 307770597
Test: manual test
Change-Id: I32422e23c8387c52e9f0cad3e5833b2cab1fa842
diff --git a/packages/SettingsLib/LintChecker/Android.bp b/packages/SettingsLib/LintChecker/Android.bp
new file mode 100644
index 0000000..eb489b1
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+ name: "SettingsLibLintChecker",
+ srcs: ["src/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
new file mode 100644
index 0000000..1f06261
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 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 com.android.settingslib.tools.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.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiPrimitiveType
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UAnnotated
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class NullabilityAnnotationsDetector : Detector(), Detector.UastScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ if (!context.isJavaFile()) return null
+
+ return object : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ if (node.isPublic() && node.name != ANONYMOUS_CONSTRUCTOR) {
+ node.verifyMethod()
+ node.verifyMethodParameters()
+ }
+ }
+
+ private fun UMethod.isPublic() = modifierList.hasModifierProperty(PsiModifier.PUBLIC)
+
+ private fun UMethod.verifyMethod() {
+ if (isConstructor) return
+ if (returnType.isPrimitive()) return
+ checkAnnotation(METHOD_MSG)
+ }
+
+ private fun UMethod.verifyMethodParameters() {
+ for (parameter in uastParameters) {
+ if (parameter.type.isPrimitive()) continue
+ parameter.checkAnnotation(PARAMETER_MSG)
+ }
+ }
+
+ private fun PsiType?.isPrimitive() = this is PsiPrimitiveType
+
+ private fun UAnnotated.checkAnnotation(message: String) {
+ val oldAnnotation = findOldNullabilityAnnotation()
+ val oldAnnotationName = oldAnnotation?.qualifiedName?.substringAfterLast('.')
+
+ if (oldAnnotationName != null) {
+ val annotation = "androidx.annotation.$oldAnnotationName"
+ reportIssue(
+ REQUIRE_NULLABILITY_ISSUE,
+ "Prefer $annotation",
+ LintFix.create()
+ .replace()
+ .range(context.getLocation(oldAnnotation))
+ .with("@$annotation")
+ .autoFix()
+ .build()
+ )
+ } else if (!hasNullabilityAnnotation()) {
+ reportIssue(REQUIRE_NULLABILITY_ISSUE, message)
+ }
+ }
+
+ private fun UElement.reportIssue(
+ issue: Issue,
+ message: String,
+ quickfixData: LintFix? = null,
+ ) {
+ context.report(
+ issue = issue,
+ scope = this,
+ location = context.getNameLocation(this),
+ message = message,
+ quickfixData = quickfixData,
+ )
+ }
+
+ private fun UAnnotated.findOldNullabilityAnnotation() =
+ uAnnotations.find { it.qualifiedName in oldAnnotations }
+
+ private fun UAnnotated.hasNullabilityAnnotation() =
+ uAnnotations.any { it.qualifiedName in validAnnotations }
+ }
+ }
+
+ private fun JavaContext.isJavaFile() = psiFile?.fileElementType.toString().startsWith("java")
+
+ companion object {
+ private val validAnnotations = arrayOf("androidx.annotation.NonNull",
+ "androidx.annotation.Nullable")
+
+ private val oldAnnotations = arrayOf("android.annotation.NonNull",
+ "android.annotation.Nullable",
+ )
+
+ private const val ANONYMOUS_CONSTRUCTOR = "<anon-init>"
+
+ private const val METHOD_MSG =
+ "Java public method return with non-primitive type must add androidx annotation. " +
+ "Example: @NonNull | @Nullable Object functionName() {}"
+
+ private const val PARAMETER_MSG =
+ "Java public method parameter with non-primitive type must add androidx " +
+ "annotation. Example: functionName(@NonNull Context context, " +
+ "@Nullable Object obj) {}"
+
+ internal val REQUIRE_NULLABILITY_ISSUE = Issue
+ .create(
+ id = "RequiresNullabilityAnnotation",
+ briefDescription = "Requires nullability annotation for function",
+ explanation = "All public java APIs should specify nullability annotations for " +
+ "methods and parameters.",
+ category = Category.CUSTOM_LINT_CHECKS,
+ priority = 3,
+ severity = Severity.WARNING,
+ androidSpecific = true,
+ implementation = Implementation(
+ NullabilityAnnotationsDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
new file mode 100644
index 0000000..e0ab24a
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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 com.android.settingslib.tools.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+class SettingsLintIssueRegistry : IssueRegistry() {
+ override val issues = listOf(NullabilityAnnotationsDetector.REQUIRE_NULLABILITY_ISSUE)
+
+ override val api: Int = CURRENT_API
+}
\ No newline at end of file