KT-12015 (Kotlin Lint False Positive for Bundle.getInt())

Java approach is not sufficient here, in Kotlin receivers can be implicit.
Use Kotlin extension/dispatch receivers to figure out the receiver type for our method call.
(cherry picked from commit cdedf1d)
This commit is contained in:
Yan Zhulanow
2016-06-10 21:35:41 +03:00
parent 530ab42de6
commit 1de05b965e
6 changed files with 90 additions and 16 deletions
@@ -166,8 +166,18 @@ open class ApiDetector : Detector(), UastScanner {
val parentClass = declaration.parent as? UClass ?: return
if (!isSdkClass(parentClass) || checkAosp(parentClass)) return
val parentInternalName = (node as? UCallExpression)?.getReceiver()?.getExpressionType()?.resolveClass(context)?.internalName
?: parentClass.internalName ?: return
var parentInternalName = parentClass.internalName
if (node is UCallExpression) {
val clazz = node.receiverType?.resolveClass(context)?.let { getSupertypeFromAndroidSdk(context, it) }
val internalName = clazz?.internalName
if (internalName != null) {
parentInternalName = internalName
}
}
if (parentInternalName == null) return
when (declaration) {
is UFunction -> {
@@ -197,6 +207,18 @@ open class ApiDetector : Detector(), UastScanner {
return mMinApi
}
private fun getSupertypeFromAndroidSdk(context: UastContext, clazz: UClass): UClass? {
tailrec fun getSuperclassFromAndroidSdk(clazz: UClass): UClass? {
if (clazz.fqName?.startsWith("android.") ?: false) return clazz
return getSuperclassFromAndroidSdk(clazz.getSuperClass(context) ?: return null)
}
val superClass = getSuperclassFromAndroidSdk(clazz)
if (superClass != null) return superClass
return clazz.superTypes.firstOrNull { it.fqName?.startsWith("android.") ?: false }?.resolve(context)
}
}
companion object {
@@ -21,6 +21,8 @@ import org.jetbrains.uast.visitor.UastVisitor
* Represents a call expression (function call, constructor call, array initializer).
*/
interface UCallExpression : UExpression, UResolvable {
val receiverType: UType?
/**
* Returns the call kind.
*/
@@ -27,6 +27,9 @@ class JavaUAssertExpression(
val condition: UExpression by lz { JavaConverter.convertOrEmpty(psi.assertCondition, this) }
val message: UExpression? by lz { JavaConverter.convertOrNull(psi.assertDescription, this) }
override val receiverType: UType?
get() = null
override val functionReference: USimpleReferenceExpression?
get() = null
@@ -26,6 +26,13 @@ class JavaUCallExpression(
override val psi: PsiMethodCallExpression,
override val parent: UElement
) : JavaAbstractUElement(), UCallExpression, PsiElementBacked, JavaUElementWithType {
override val receiverType: UType?
get() {
val qualifiedExpression = parent as? UQualifiedExpression ?: return null
if (qualifiedExpression.selector != this) return null
return qualifiedExpression.receiver.getExpressionType()
}
override val kind: UastCallKind
get() = UastCallKind.FUNCTION_CALL
@@ -63,6 +70,9 @@ class JavaConstructorUCallExpression(
}
}
override val receiverType: UType?
get() = null
override val functionReference: USimpleReferenceExpression?
get() = null
@@ -119,6 +129,9 @@ class JavaArrayInitializerUCallExpression(
override val psi: PsiArrayInitializerExpression,
override val parent: UElement
) : JavaAbstractUElement(), UCallExpression, PsiElementBacked, JavaUElementWithType, JavaEvaluatableUElement {
override val receiverType: UType?
get() = null
override val functionReference: USimpleReferenceExpression?
get() = null
@@ -150,6 +163,9 @@ class JavaAnnotationArrayInitializerUCallExpression(
override val psi: PsiArrayInitializerMemberValue,
override val parent: UElement
) : JavaAbstractUElement(), UCallExpression, PsiElementBacked, JavaUElementWithType, JavaEvaluatableUElement {
override val receiverType: UType?
get() = null
override val kind: UastCallKind
get() = UastCallKind.ARRAY_INITIALIZER
@@ -30,7 +30,15 @@ class KotlinUFunctionCallExpression(
override val psi: KtCallExpression,
override val parent: UElement
) : KotlinAbstractUElement(), UCallExpression, PsiElementBacked, KotlinUElementWithType {
override val functionName: String? by lz { resolveCall()?.resultingDescriptor?.name?.asString().orAnonymous() }
private val resolvedCall by lz { psi.getResolvedCall(psi.analyze(BodyResolveMode.PARTIAL)) }
override val receiverType by lz {
val resolvedCall = this.resolvedCall ?: return@lz null
val receiver = resolvedCall.extensionReceiver ?: resolvedCall.dispatchReceiver ?: return@lz null
KotlinConverter.convert(receiver.type, psi.project, null)
}
override val functionName: String? by lz { resolvedCall?.resultingDescriptor?.name?.asString().orAnonymous() }
override fun matchesFunctionName(name: String) = functionName == name
override val functionNameElement by lz { psi.calleeExpression?.let { KotlinConverter.convert(it, this) } }
@@ -56,14 +64,13 @@ class KotlinUFunctionCallExpression(
override val typeArguments by lz { psi.typeArguments.map { KotlinConverter.convert(it.typeReference, this) } }
override val kind by lz {
when (resolveCall()?.resultingDescriptor) {
when (resolvedCall?.resultingDescriptor) {
is ConstructorDescriptor -> UastCallKind.CONSTRUCTOR_CALL
else -> UastCallKind.FUNCTION_CALL
}
}
override fun resolve(context: UastContext): UFunction? {
val resolvedCall = resolveCall()
val descriptor = resolvedCall?.resultingDescriptor ?: return null
val source = descriptor.toSource() ?: return null
@@ -74,9 +81,7 @@ class KotlinUFunctionCallExpression(
}
return context.convert(source) as? UFunction
}
private fun resolveCall() = psi.getResolvedCall(psi.analyze(BodyResolveMode.PARTIAL))
}
}
class KotlinUComponentFunctionCallExpression(
@@ -84,14 +89,35 @@ class KotlinUComponentFunctionCallExpression(
n: Int,
override val parent: UElement
) : UCallExpression, PsiElementBacked {
override val valueArgumentCount = 0
override val valueArguments = emptyList<UExpression>()
override val typeArgumentCount = 0
override val typeArguments = emptyList<UType>()
override val classReference = null
override val receiverType: UType?
get() = null
override val valueArgumentCount: Int
get() = 0
override val valueArguments: List<UExpression>
get() = emptyList()
override val typeArgumentCount: Int
get() = 0
override val typeArguments: List<UType>
get() = emptyList()
override val classReference: USimpleReferenceExpression?
get() = null
override val functionName = "component$n"
override val functionReference by lz { KotlinStringUSimpleReferenceExpression(functionName, this) }
override val functionNameElement = null
override val kind = UastCallKind.FUNCTION_CALL
override fun resolve(context: UastContext) = null
override val functionNameElement: UElement?
get() = null
override val kind: UastCallKind
get() = UastCallKind.FUNCTION_CALL
override fun resolve(context: UastContext): UFunction? {
return null
}
}
@@ -51,6 +51,11 @@ class PropertyAsCallAndroidUastVisitorExtension : UastVisitorExtension {
override val parent = element.parent
override val psi = ktElement
override val receiverType by lz {
val type = (resolvedCall.extensionReceiver ?: resolvedCall.dispatchReceiver)?.type ?: return@lz null
KotlinConverter.convert(type, psi.project, null)
}
override val functionReference = KotlinNameUSimpleReferenceExpression(
expr.psi, expr.identifier, expr.parent, accessorDescriptor)