Lint: Fix KT-11919 ("Missing Parcelable CREATOR field" rule is not accurate)
This commit is contained in:
@@ -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" }
|
||||
}
|
||||
|
||||
+27
-6
@@ -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
@@ -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) {}
|
||||
|
||||
Reference in New Issue
Block a user