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();
|
String name = reference.getName();
|
||||||
if (name.equals("Parcelable")) {
|
if (name.equals("Parcelable")) {
|
||||||
UVariable field = UastUtils.findStaticMemberOfType(node, "CREATOR", UVariable.class);
|
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
|
// Make doubly sure that we're really implementing
|
||||||
// android.os.Parcelable
|
// android.os.Parcelable
|
||||||
UClass parcelable = reference.resolve(mContext);
|
UClass parcelable = reference.resolve(mContext);
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ open class UastModifier(val name: String) {
|
|||||||
val VARARG = UastModifier("vararg")
|
val VARARG = UastModifier("vararg")
|
||||||
@JvmField
|
@JvmField
|
||||||
val OVERRIDE = UastModifier("override")
|
val OVERRIDE = UastModifier("override")
|
||||||
|
@JvmField
|
||||||
|
val FIELD = UastModifier("field")
|
||||||
|
|
||||||
val VALUES = listOf(ABSTRACT, STATIC, FINAL, IMMUTABLE, VARARG, OVERRIDE)
|
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 {
|
internal fun PsiModifierListOwner.hasModifier(modifier: UastModifier): Boolean {
|
||||||
|
if (modifier == UastModifier.FIELD && this is PsiField) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (modifier == UastModifier.OVERRIDE && this is PsiAnnotationOwner) {
|
if (modifier == UastModifier.OVERRIDE && this is PsiAnnotationOwner) {
|
||||||
return this.annotations.any { it.qualifiedName == "java.lang.Override" }
|
return this.annotations.any { it.qualifiedName == "java.lang.Override" }
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-6
@@ -17,17 +17,27 @@
|
|||||||
package org.jetbrains.kotlin.uast
|
package org.jetbrains.kotlin.uast
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
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.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
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.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
|
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
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.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.uast.kinds.KotlinUastVisibilities
|
import org.jetbrains.kotlin.uast.kinds.KotlinUastVisibilities
|
||||||
import org.jetbrains.uast.*
|
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(
|
private val MODIFIER_MAP = mapOf(
|
||||||
UastModifier.ABSTRACT to KtTokens.ABSTRACT_KEYWORD,
|
UastModifier.ABSTRACT to KtTokens.ABSTRACT_KEYWORD,
|
||||||
UastModifier.OVERRIDE to KtTokens.OVERRIDE_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 ||
|
if (this is KtDeclaration && (parent is KtObjectDeclaration ||
|
||||||
parent is KtClassBody && parent?.parent is KtObjectDeclaration)) {
|
parent is KtClassBody && parent?.parent is KtObjectDeclaration)) {
|
||||||
return true
|
return hasAnnotations(JVM_STATIC_FQNAME, JVM_FIELD_FQNAME)
|
||||||
}
|
}
|
||||||
return false
|
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.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
|
val javaModifier = MODIFIER_MAP[modifier] ?: return false
|
||||||
return hasModifier(javaModifier)
|
return hasModifier(javaModifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <T> runReadAction(action: () -> T): T {
|
private fun KtElement.hasAnnotations(vararg annotationFqNames: String): Boolean {
|
||||||
return ApplicationManager.getApplication().runReadAction<T>(action)
|
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? {
|
internal fun KtElement?.resolveCallToUDeclaration(context: UastContext): UDeclaration? {
|
||||||
if (this == null) return null
|
if (this == null) return null
|
||||||
val resolvedCall = this.getResolvedCall(analyze(BodyResolveMode.PARTIAL)) ?: 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 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 {
|
internal fun DeclarationDescriptor.toSource() = try {
|
||||||
DescriptorToSourceUtils.descriptorToDeclaration(this)
|
DescriptorToSourceUtils.descriptorToDeclaration(this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.uast
|
|||||||
|
|
||||||
import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
|
import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
|
||||||
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
|
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.InTextDirectivesUtils.findStringWithPrefixes
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -26,9 +27,15 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
|
|||||||
|
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
ConfigLibraryUtil.configureKotlinRuntime(myModule)
|
||||||
AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
|
AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun tearDown() {
|
||||||
|
ConfigLibraryUtil.unConfigureKotlinRuntime(myModule)
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
fun doTest(filename: String) {
|
fun doTest(filename: String) {
|
||||||
val ktFile = File(filename)
|
val ktFile = File(filename)
|
||||||
val mainInspectionClassName = findStringWithPrefixes(ktFile.readText(), "// INSPECTION_CLASS: ") ?: error("Empty class name")
|
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) {}
|
override fun writeToParcel(arg0: Parcel, arg1: Int) {}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@JvmField
|
||||||
val CREATOR: Parcelable.Creator<String> = object : Parcelable.Creator<String> {
|
val CREATOR: Parcelable.Creator<String> = object : Parcelable.Creator<String> {
|
||||||
override fun newArray(size: Int) = null!!
|
override fun newArray(size: Int) = null!!
|
||||||
override fun createFromParcel(source: Parcel?) = null!!
|
override fun createFromParcel(source: Parcel?) = null!!
|
||||||
@@ -27,10 +28,72 @@ internal class MyParcelable3 : Parcelable {
|
|||||||
override fun writeToParcel(arg0: Parcel, arg1: Int) {}
|
override fun writeToParcel(arg0: Parcel, arg1: Int) {}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@JvmField
|
||||||
val CREATOR = 0 // Wrong type
|
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 {
|
internal abstract class MyParcelable4 : Parcelable {
|
||||||
override fun describeContents() = 0
|
override fun describeContents() = 0
|
||||||
override fun writeToParcel(arg0: Parcel, arg1: Int) {}
|
override fun writeToParcel(arg0: Parcel, arg1: Int) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user