blob: 32eb8e52bb917b73295850972d71cf49e5113825 [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.detector.api
import com.android.SdkConstants.ATTR_VALUE
import com.android.tools.lint.client.api.ResourceReference
import com.android.tools.lint.detector.api.ConstantEvaluator.LastAssignmentFinder.LAST_ASSIGNMENT_VALUE_UNKNOWN
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiLocalVariable
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiParameter
import com.intellij.psi.PsiVariable
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UDeclaration
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UFile
import org.jetbrains.uast.ULiteralExpression
import org.jetbrains.uast.UPrefixExpression
import org.jetbrains.uast.UQualifiedReferenceExpression
import org.jetbrains.uast.UReferenceExpression
import org.jetbrains.uast.UResolvable
import org.jetbrains.uast.USimpleNameReferenceExpression
import org.jetbrains.uast.UUnaryExpression
import org.jetbrains.uast.UVariable
import org.jetbrains.uast.UastFacade
import org.jetbrains.uast.UastPrefixOperator
import org.jetbrains.uast.getContainingUMethod
import org.jetbrains.uast.getParentOfType
import org.jetbrains.uast.getUastContext
import org.jetbrains.uast.toUElementOfType
class UastLintUtils {
companion object {
@JvmStatic
fun UElement.tryResolveUDeclaration(): UDeclaration? {
return (this as? UResolvable)?.resolve().toUElementOfType()
}
/** Returns the containing file for the given element */
@JvmStatic
fun getContainingFile(
context: JavaContext,
element: PsiElement?
): PsiFile? {
if (element == null) {
return null
}
val containingFile = element.containingFile
return if (containingFile != context.psiFile) {
getContainingFile(element)
} else containingFile
}
/** Returns the containing file for the given element */
@JvmStatic
fun getPsiFile(file: UFile?): PsiFile? {
return file?.let { getContainingFile(it.psi) }
}
/** Returns the containing file for the given element */
@JvmStatic
fun getContainingFile(element: PsiElement?): PsiFile? {
if (element == null) {
return null
}
val containingFile = element as? PsiFile ?: element.containingFile
// In Kotlin files identifiers are sometimes using LightElements that are hosted in
// a dummy file, these do not have the right PsiFile as containing elements
val cls = containingFile.javaClass
val name = cls.name
if (name.startsWith(
"org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration"
)
) {
try {
val declaredField = cls.superclass.getDeclaredField("ktFile")
declaredField.isAccessible = true
val o = declaredField.get(containingFile)
if (o is PsiFile) {
return o
}
} catch (ignore: Throwable) {
}
} else if (name == "org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass") {
try {
val declaredField = cls.getDeclaredField("ktFile")
declaredField.isAccessible = true
val o = declaredField.get(containingFile)
if (o is PsiFile) {
return o
}
} catch (ignore: Throwable) {
}
}
return containingFile
}
@JvmStatic
fun getQualifiedName(element: PsiElement): String? = when (element) {
is PsiClass -> element.qualifiedName
is PsiMethod -> element.containingClass?.let { getQualifiedName(it) }?.let { "$it.${element.name}" }
is PsiField -> element.containingClass?.let { getQualifiedName(it) }?.let { "$it.${element.name}" }
else -> null
}
@JvmStatic
fun resolve(expression: ExternalReferenceExpression, context: UElement): PsiElement? {
val declaration = context.getParentOfType<UDeclaration>(UDeclaration::class.java) ?: return null
return expression.resolve(declaration.psi)
}
@JvmStatic
fun getClassName(type: PsiClassType): String {
val psiClass = type.resolve()
return psiClass?.let { getClassName(it) } ?: type.className
}
@JvmStatic
fun getClassName(psiClass: PsiClass): String {
val stringBuilder = StringBuilder()
stringBuilder.append(psiClass.name)
var currPsiClass = psiClass.containingClass
while (currPsiClass != null) {
stringBuilder.insert(0, currPsiClass.name + ".")
currPsiClass = currPsiClass.containingClass
}
return stringBuilder.toString()
}
@JvmStatic
fun findLastAssignment(
variable: PsiVariable,
call: UElement
): UExpression? {
var currVariable = variable
var lastAssignment: UElement? = null
if (currVariable is UVariable) {
currVariable = currVariable.psi
}
if (!currVariable.hasModifierProperty(PsiModifier.FINAL) && (currVariable is PsiLocalVariable || currVariable is PsiParameter)) {
val containingFunction = call.getContainingUMethod()
if (containingFunction != null) {
val finder = ConstantEvaluator.LastAssignmentFinder(
currVariable, call, null, -1
)
containingFunction.accept(finder)
lastAssignment = finder.lastAssignment
}
} else {
val context = call.getUastContext()
lastAssignment = context.getInitializerBody(currVariable)
}
return if (lastAssignment is UExpression) lastAssignment
else null
}
@JvmStatic
fun getReferenceName(expression: UReferenceExpression): String? {
if (expression is USimpleNameReferenceExpression) {
return expression.identifier
} else if (expression is UQualifiedReferenceExpression) {
val selector = expression.selector
if (selector is USimpleNameReferenceExpression) {
return selector.identifier
}
}
return null
}
@JvmStatic
fun findLastValue(
variable: PsiVariable,
call: UElement,
evaluator: ConstantEvaluator
): Any? {
var value: Any? = null
if (!variable.hasModifierProperty(PsiModifier.FINAL) && (variable is PsiLocalVariable || variable is PsiParameter)) {
val containingFunction = call.getContainingUMethod()
if (containingFunction != null) {
val body = containingFunction.uastBody
if (body != null) {
val finder = ConstantEvaluator.LastAssignmentFinder(
variable, call, evaluator, 1
)
body.accept(finder)
value = finder.currentValue
if (value == null && finder.lastAssignment != null) {
// Special return value: variable was assigned but we don't know
// the value
return LAST_ASSIGNMENT_VALUE_UNKNOWN
}
}
}
} else {
val initializer = UastFacade.getInitializerBody(variable)
if (initializer != null) {
value = initializer.evaluate()
}
}
return value
}
@JvmStatic
fun toAndroidReferenceViaResolve(element: UElement): ResourceReference? {
return ResourceReference.get(element)
}
@JvmStatic
fun areIdentifiersEqual(first: UExpression, second: UExpression): Boolean {
val firstIdentifier = getIdentifier(first)
val secondIdentifier = getIdentifier(second)
return (firstIdentifier != null &&
secondIdentifier != null &&
firstIdentifier == secondIdentifier)
}
@JvmStatic
fun getIdentifier(expression: UExpression): String? =
when (expression) {
is ULiteralExpression -> expression.asRenderString()
is UQualifiedReferenceExpression -> {
val receiverIdentifier = getIdentifier(expression.receiver)
val selectorIdentifier = getIdentifier(expression.selector)
if (receiverIdentifier == null || selectorIdentifier == null) {
null
} else "$receiverIdentifier.$selectorIdentifier"
}
else -> null
}
@JvmStatic
fun isNumber(argument: UElement): Boolean =
when (argument) {
is ULiteralExpression -> argument.value is Number
is UPrefixExpression -> isNumber(argument.operand)
else -> false
}
@JvmStatic
fun isZero(argument: UElement): Boolean {
if (argument is ULiteralExpression) {
val value = argument.value
return value is Number && value.toInt() == 0
}
return false
}
@JvmStatic
fun isMinusOne(argument: UElement): Boolean {
return if (argument is UUnaryExpression) {
val operand = argument.operand
if (operand is ULiteralExpression && argument.operator === UastPrefixOperator.UNARY_MINUS) {
val value = operand.value
value is Number && value.toInt() == 1
} else {
false
}
} else {
false
}
}
@JvmStatic
fun getAnnotationValue(annotation: UAnnotation): UExpression? {
return annotation.findDeclaredAttributeValue(ATTR_VALUE)
}
@JvmStatic
fun getLongAttribute(
context: JavaContext,
annotation: UAnnotation,
name: String,
defaultValue: Long
): Long {
return getAnnotationLongValue(annotation, name, defaultValue)
}
@JvmStatic
fun getDoubleAttribute(
context: JavaContext,
annotation: UAnnotation,
name: String,
defaultValue: Double
): Double {
return getAnnotationDoubleValue(annotation, name, defaultValue)
}
@JvmStatic
fun getBoolean(
context: JavaContext,
annotation: UAnnotation,
name: String,
defaultValue: Boolean
): Boolean {
return getAnnotationBooleanValue(annotation, name, defaultValue)
}
@JvmStatic
fun getAnnotationBooleanValue(
annotation: UAnnotation?,
name: String
): Boolean? {
return AnnotationValuesExtractor
.getAnnotationValuesExtractor(annotation)
.getAnnotationBooleanValue(annotation, name)
}
@JvmStatic
fun getAnnotationBooleanValue(
annotation: UAnnotation?,
name: String,
defaultValue: Boolean
): Boolean {
val value = getAnnotationBooleanValue(annotation, name)
return value ?: defaultValue
}
@JvmStatic
fun getAnnotationLongValue(
annotation: UAnnotation?,
name: String
): Long? {
return AnnotationValuesExtractor
.getAnnotationValuesExtractor(annotation)
.getAnnotationLongValue(annotation, name)
}
@JvmStatic
fun getAnnotationLongValue(
annotation: UAnnotation?,
name: String,
defaultValue: Long
): Long {
val value = getAnnotationLongValue(annotation, name)
return value ?: defaultValue
}
@JvmStatic
fun getAnnotationDoubleValue(
annotation: UAnnotation?,
name: String
): Double? {
return AnnotationValuesExtractor
.getAnnotationValuesExtractor(annotation)
.getAnnotationDoubleValue(annotation, name)
}
@JvmStatic
fun getAnnotationDoubleValue(
annotation: UAnnotation?,
name: String,
defaultValue: Double
): Double {
val value = getAnnotationDoubleValue(annotation, name)
return value ?: defaultValue
}
@JvmStatic
fun getAnnotationStringValue(
annotation: UAnnotation?,
name: String
): String? {
return AnnotationValuesExtractor
.getAnnotationValuesExtractor(annotation)
.getAnnotationStringValue(annotation, name)
}
@JvmStatic
fun getAnnotationStringValues(
annotation: UAnnotation?,
name: String
): Array<String>? {
return AnnotationValuesExtractor
.getAnnotationValuesExtractor(annotation)
.getAnnotationStringValues(annotation, name)
}
@JvmStatic
fun containsAnnotation(list: List<UAnnotation>, annotation: UAnnotation): Boolean =
list.stream().anyMatch { e -> e === annotation }
@JvmStatic
fun containsAnnotation(
list: List<UAnnotation>,
qualifiedName: String
): Boolean = list.stream().anyMatch { e -> e.qualifiedName == qualifiedName }
}
}