Lint: Fix KT-11919 ("Missing Parcelable CREATOR field" rule is not accurate)

This commit is contained in:
Yan Zhulanow
2016-04-19 16:14:34 +03:00
parent 33434a3c5b
commit a2b1eb7ee8
6 changed files with 114 additions and 7 deletions
@@ -90,7 +90,18 @@ public class ParcelDetector extends Detector implements UastScanner {
String name = reference.getName();
if (name.equals("Parcelable")) {
UVariable field = UastUtils.findStaticMemberOfType(node, "CREATOR", UVariable.class);
if (field == null) {
boolean hasField = field != null && field.hasModifier(UastModifier.FIELD);
boolean hasNamedCompanionObject = false;
if (!hasField) {
for (UClass companion : node.getCompanions()) {
if (companion.getName().equals("CREATOR")) {
hasNamedCompanionObject = true;
break;
}
}
}
if (!hasField && !hasNamedCompanionObject) {
// Make doubly sure that we're really implementing
// android.os.Parcelable
UClass parcelable = reference.resolve(mContext);
@@ -34,6 +34,8 @@ open class UastModifier(val name: String) {
val VARARG = UastModifier("vararg")
@JvmField
val OVERRIDE = UastModifier("override")
@JvmField
val FIELD = UastModifier("field")
val VALUES = listOf(ABSTRACT, STATIC, FINAL, IMMUTABLE, VARARG, OVERRIDE)
}
@@ -27,6 +27,9 @@ private val MODIFIER_MAP = mapOf(
)
internal fun PsiModifierListOwner.hasModifier(modifier: UastModifier): Boolean {
if (modifier == UastModifier.FIELD && this is PsiField) {
return true;
}
if (modifier == UastModifier.OVERRIDE && this is PsiAnnotationOwner) {
return this.annotations.any { it.qualifiedName == "java.lang.Override" }
}
@@ -17,17 +17,27 @@
package org.jetbrains.kotlin.uast
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.uast.kinds.KotlinUastVisibilities
import org.jetbrains.uast.*
private val JVM_STATIC_FQNAME = "kotlin.jvm.JvmStatic"
private val JVM_FIELD_FQNAME = "kotlin.jvm.JvmField"
private val MODIFIER_MAP = mapOf(
UastModifier.ABSTRACT to KtTokens.ABSTRACT_KEYWORD,
UastModifier.OVERRIDE to KtTokens.OVERRIDE_KEYWORD
@@ -51,22 +61,35 @@ internal fun KtModifierListOwner.hasModifier(modifier: UastModifier): Boolean {
}
if (this is KtDeclaration && (parent is KtObjectDeclaration ||
parent is KtClassBody && parent?.parent is KtObjectDeclaration)) {
return true
return hasAnnotations(JVM_STATIC_FQNAME, JVM_FIELD_FQNAME)
}
return false
}
if (modifier == UastModifier.FIELD) return hasAnnotations(JVM_FIELD_FQNAME)
if (modifier == UastModifier.FINAL) return hasModifier(KtTokens.FINAL_KEYWORD)
if (modifier == UastModifier.IMMUTABLE && this is KtVariableDeclaration && !this.isVar) return true
if (modifier == UastModifier.FINAL && hasModifier(KtTokens.FINAL_KEYWORD)) return true
val javaModifier = MODIFIER_MAP[modifier] ?: return false
return hasModifier(javaModifier)
}
internal fun <T> runReadAction(action: () -> T): T {
return ApplicationManager.getApplication().runReadAction<T>(action)
private fun KtElement.hasAnnotations(vararg annotationFqNames: String): Boolean {
if (this !is KtAnnotated) return false
for (annotationFqName in annotationFqNames) {
val annotationEntry = findAnnotation(FqName(annotationFqName)) ?: continue
val bindingContext = this.analyze(BodyResolveMode.PARTIAL)
val annotationDescriptor = bindingContext[BindingContext.ANNOTATION, annotationEntry] ?: continue
val classifierDescriptor = annotationDescriptor.type.constructor.declarationDescriptor ?: continue
val fqName = DescriptorUtils.getFqName(classifierDescriptor).asString()
return fqName == annotationFqName
}
return false
}
internal fun KtElement?.resolveCallToUDeclaration(context: UastContext): UDeclaration? {
if (this == null) return null
val resolvedCall = this.getResolvedCall(analyze(BodyResolveMode.PARTIAL)) ?: return null
@@ -81,8 +104,6 @@ internal inline fun String?.orAnonymous(kind: String = ""): String {
internal fun KtAnnotated.getUastAnnotations(parent: UElement) = annotationEntries.map { KotlinUAnnotation(it, parent) }
internal fun <T> singletonListOrEmpty(element: T?) = if (element != null) listOf(element) else emptyList<T>()
internal fun DeclarationDescriptor.toSource() = try {
DescriptorToSourceUtils.descriptorToDeclaration(this)
} catch (e: Exception) {
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.uast
import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
@@ -26,9 +27,15 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
override fun setUp() {
super.setUp()
ConfigLibraryUtil.configureKotlinRuntime(myModule)
AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
}
override fun tearDown() {
ConfigLibraryUtil.unConfigureKotlinRuntime(myModule)
super.tearDown()
}
fun doTest(filename: String) {
val ktFile = File(filename)
val mainInspectionClassName = findStringWithPrefixes(ktFile.readText(), "// INSPECTION_CLASS: ") ?: error("Empty class name")
+63
View File
@@ -15,6 +15,7 @@ internal class MyParcelable2 : Parcelable {
override fun writeToParcel(arg0: Parcel, arg1: Int) {}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<String> = object : Parcelable.Creator<String> {
override fun newArray(size: Int) = null!!
override fun createFromParcel(source: Parcel?) = null!!
@@ -27,10 +28,72 @@ internal class MyParcelable3 : Parcelable {
override fun writeToParcel(arg0: Parcel, arg1: Int) {}
companion object {
@JvmField
val CREATOR = 0 // Wrong type
}
}
class RecyclerViewScrollPosition(val position: Int, val topOffset: Int): Parcelable {
override fun describeContents(): Int = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(position)
dest.writeInt(topOffset)
}
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<RecyclerViewScrollPosition> {
override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition {
val position = parcel.readInt()
val topOffset = parcel.readInt()
return RecyclerViewScrollPosition(position, topOffset)
}
override fun newArray(size: Int): Array<RecyclerViewScrollPosition?> = arrayOfNulls(size)
}
}
}
class <error>RecyclerViewScrollPositionWithoutJvmF</error>(val position: Int, val topOffset: Int): Parcelable {
override fun describeContents(): Int = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(position)
dest.writeInt(topOffset)
}
companion object {
val CREATOR = object : Parcelable.Creator<RecyclerViewScrollPosition> {
override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition {
val position = parcel.readInt()
val topOffset = parcel.readInt()
return RecyclerViewScrollPosition(position, topOffset)
}
override fun newArray(size: Int): Array<RecyclerViewScrollPosition?> = arrayOfNulls(size)
}
}
}
class RecyclerViewScrollPosition2(val position: Int, val topOffset: Int): Parcelable {
override fun describeContents(): Int = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(position)
dest.writeInt(topOffset)
}
companion object CREATOR: Parcelable.Creator<RecyclerViewScrollPosition> {
override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition {
val position = parcel.readInt()
val topOffset = parcel.readInt()
return RecyclerViewScrollPosition(position, topOffset)
}
override fun newArray(size: Int): Array<RecyclerViewScrollPosition?> = arrayOfNulls(size)
}
}
internal abstract class MyParcelable4 : Parcelable {
override fun describeContents() = 0
override fun writeToParcel(arg0: Parcel, arg1: Int) {}