diff options
author | 2023-12-13 22:04:28 +0800 | |
---|---|---|
committer | 2024-01-08 18:05:40 +0800 | |
commit | 7b48eb343b9dc515e38ef58f423d3d98bdd96858 (patch) | |
tree | a7990165121ceb40135a2c0874bbc11d5336fda3 | |
parent | 60b6a74df90709a2a3edf94e665df8674708773a (diff) |
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
3 files changed, 207 insertions, 0 deletions
diff --git a/packages/SettingsLib/LintChecker/Android.bp b/packages/SettingsLib/LintChecker/Android.bp new file mode 100644 index 000000000000..eb489b1de380 --- /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 000000000000..1f062619b261 --- /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 000000000000..e0ab24afee5a --- /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 |