diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java index a6a92b1d6f1..7682a66774b 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java @@ -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); diff --git a/plugins/uast-common/src/org/jetbrains/uast/kinds/UastModifier.kt b/plugins/uast-common/src/org/jetbrains/uast/kinds/UastModifier.kt index 973a1fa63d9..3d43747bf8f 100644 --- a/plugins/uast-common/src/org/jetbrains/uast/kinds/UastModifier.kt +++ b/plugins/uast-common/src/org/jetbrains/uast/kinds/UastModifier.kt @@ -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) } diff --git a/plugins/uast-java/src/org/jetbrains/uast/java/internal/javaInternalUastUtils.kt b/plugins/uast-java/src/org/jetbrains/uast/java/internal/javaInternalUastUtils.kt index 182b087ecd5..3f8cd9def20 100644 --- a/plugins/uast-java/src/org/jetbrains/uast/java/internal/javaInternalUastUtils.kt +++ b/plugins/uast-java/src/org/jetbrains/uast/java/internal/javaInternalUastUtils.kt @@ -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" } } diff --git a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/internal/kotlinInternalUastUtils.kt b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/internal/kotlinInternalUastUtils.kt index 4d5c5e09318..ddc1afbb2f5 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/internal/kotlinInternalUastUtils.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/internal/kotlinInternalUastUtils.kt @@ -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 runReadAction(action: () -> T): T { - return ApplicationManager.getApplication().runReadAction(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 singletonListOrEmpty(element: T?) = if (element != null) listOf(element) else emptyList() - internal fun DeclarationDescriptor.toSource() = try { DescriptorToSourceUtils.descriptorToDeclaration(this) } catch (e: Exception) { diff --git a/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt index e2dff1216b0..25750cb366a 100644 --- a/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt +++ b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt @@ -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") diff --git a/plugins/uast-kotlin/testData/lint/parcel.kt b/plugins/uast-kotlin/testData/lint/parcel.kt index 4434a6bf8a0..4ba5a4eda46 100644 --- a/plugins/uast-kotlin/testData/lint/parcel.kt +++ b/plugins/uast-kotlin/testData/lint/parcel.kt @@ -15,6 +15,7 @@ internal class MyParcelable2 : Parcelable { override fun writeToParcel(arg0: Parcel, arg1: Int) {} companion object { + @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 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 { + override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition { + val position = parcel.readInt() + val topOffset = parcel.readInt() + return RecyclerViewScrollPosition(position, topOffset) + } + + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + + } +} + +class RecyclerViewScrollPositionWithoutJvmF(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 { + override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition { + val position = parcel.readInt() + val topOffset = parcel.readInt() + return RecyclerViewScrollPosition(position, topOffset) + } + + override fun newArray(size: Int): Array = 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 { + override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition { + val position = parcel.readInt() + val topOffset = parcel.readInt() + return RecyclerViewScrollPosition(position, topOffset) + } + + override fun newArray(size: Int): Array = arrayOfNulls(size) + } +} + internal abstract class MyParcelable4 : Parcelable { override fun describeContents() = 0 override fun writeToParcel(arg0: Parcel, arg1: Int) {}