blob: 56436644707768a0fc6f6bb1e378c46b111dd892 [file] [log] [blame]
/*
* Copyright (C) 2019 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.tools.lint.checks.studio
import com.android.tools.lint.client.api.JavaEvaluator
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.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UReferenceExpression
import org.jetbrains.uast.getContainingUMethod
/**
* Detects Lint code that doesn't handle external annotations correctly.
*/
class ExternalAnnotationsDetector : Detector(), SourceCodeScanner {
companion object Issues {
@JvmField
val ISSUE = Issue.create(
id = "ExternalAnnotations",
briefDescription = "External annotations not considered",
explanation = """
Lint supports XML files with "external annotations", which means any detectors that \
recognize certain annotations should get them from `JavaEvaluator.getAllAnnotations` \
and not by calling `uAnnotations` directly on UAST or PSI elements.
""",
severity = Severity.ERROR,
implementation = Implementation(
ExternalAnnotationsDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
private val relevantClasses = listOf(
"com.intellij.psi.PsiModifierListOwner",
"com.intellij.psi.PsiAnnotationOwner",
"org.jetbrains.uast.UAnnotated",
"com.intellij.lang.jvm.JvmAnnotatedElement"
)
}
override fun getApplicableMethodNames() = listOf("getAnnotations", "getUAnnotations")
override fun getApplicableReferenceNames() = listOf("annotations", "uAnnotations")
override fun visitMethodCall(
context: JavaContext,
node: UCallExpression,
method: PsiMethod
) {
check(node, method, context)
}
override fun visitReference(
context: JavaContext,
reference: UReferenceExpression,
referenced: PsiElement
) {
check(reference, referenced as? PsiMember ?: return, context)
}
private fun check(
expression: UExpression,
member: PsiMember,
context: JavaContext
) {
val evaluator = context.evaluator
if (relevantClasses.any { evaluator.isMemberInClass(member, it) } &&
isRelevantCaller(expression, evaluator)) {
context.report(
ISSUE,
expression,
context.getLocation(expression),
"${member.name} used instead of `JavaContext.getAllAnnotations`."
)
}
}
private fun isRelevantCaller(node: UExpression, evaluator: JavaEvaluator): Boolean {
val callerClass = node.getContainingUMethod()?.containingClass ?: return false
return evaluator.inheritsFrom(callerClass, Detector::class.java.name, false) ||
callerClass.qualifiedName.orEmpty().startsWith("com.android.tools.lint.")
}
}