Skip to content

Commit c1c6242

Browse files
committed
Parcelable: Add declaration checker
1 parent b4123aa commit c1c6242

File tree

14 files changed

+421
-0
lines changed

14 files changed

+421
-0
lines changed

generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,10 @@ fun main(args: Array<String>) {
13251325
testClass<AbstractAndroidExtractionTest> {
13261326
model("android/extraction", recursive = false, extension = null)
13271327
}
1328+
1329+
testClass<AbstractParcelCheckerTest> {
1330+
model("android/parcel/checker", excludeParentDirs = true)
1331+
}
13281332
}
13291333

13301334
testGroup("idea/idea-android/tests", "idea/testData") {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2010-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.android.parcel
18+
19+
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid
20+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
21+
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
22+
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
23+
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
24+
import org.jetbrains.kotlin.lexer.KtTokens
25+
import org.jetbrains.kotlin.name.FqName
26+
import org.jetbrains.kotlin.psi.KtClass
27+
import org.jetbrains.kotlin.psi.KtClassOrObject
28+
import org.jetbrains.kotlin.psi.KtDeclaration
29+
import org.jetbrains.kotlin.psi.KtProperty
30+
import org.jetbrains.kotlin.resolve.BindingContext
31+
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
32+
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
33+
import org.jetbrains.kotlin.types.TypeUtils
34+
35+
private val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable")
36+
37+
class ParcelableDeclarationChecker : SimpleDeclarationChecker {
38+
private companion object {
39+
private val TRANSIENT_FQNAME = FqName(Transient::class.java.canonicalName)
40+
}
41+
42+
override fun check(
43+
declaration: KtDeclaration,
44+
descriptor: DeclarationDescriptor,
45+
diagnosticHolder: DiagnosticSink,
46+
bindingContext: BindingContext
47+
) {
48+
when (descriptor) {
49+
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder)
50+
is PropertyDescriptor -> {
51+
val containingClass = descriptor.containingDeclaration as? ClassDescriptor
52+
val ktProperty = declaration as? KtProperty
53+
if (containingClass != null && ktProperty != null) {
54+
checkParcelableClassProperty(descriptor, containingClass, ktProperty, diagnosticHolder)
55+
}
56+
}
57+
}
58+
}
59+
60+
private fun checkParcelableClassProperty(
61+
property: PropertyDescriptor,
62+
containingClass: ClassDescriptor,
63+
declaration: KtProperty,
64+
diagnosticHolder: DiagnosticSink
65+
) {
66+
if (!containingClass.isMagicParcelable) return
67+
68+
// Do not report on calculated properties
69+
if (declaration.getter?.hasBody() == true) return
70+
71+
if (!property.annotations.hasAnnotation(TRANSIENT_FQNAME)) {
72+
diagnosticHolder.report(ErrorsAndroid.PROPERTY_WONT_BE_SERIALIZED.on(declaration.nameIdentifier ?: declaration))
73+
}
74+
}
75+
76+
private fun checkParcelableClass(descriptor: ClassDescriptor, declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) {
77+
if (!descriptor.isMagicParcelable) return
78+
79+
if (declaration !is KtClass || (declaration.isAnnotation() || declaration.isInterface() || declaration.isEnum())) {
80+
val reportElement = (declaration as? KtClassOrObject)?.nameIdentifier ?: declaration
81+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_SHOULD_BE_CLASS.on(reportElement))
82+
return
83+
}
84+
85+
val sealedOrAbstract = declaration.modifierList?.let { it.getModifier(KtTokens.ABSTRACT_KEYWORD) ?: it.getModifier(KtTokens.SEALED_KEYWORD) }
86+
if (sealedOrAbstract != null) {
87+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_SHOULD_BE_INSTANTIABLE.on(sealedOrAbstract))
88+
}
89+
90+
if (declaration.isInner()) {
91+
val reportElement = declaration.modifierList?.getModifier(KtTokens.INNER_KEYWORD) ?: declaration.nameIdentifier ?: declaration
92+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CANT_BE_INNER_CLASS.on(reportElement))
93+
}
94+
95+
if (declaration.isLocal) {
96+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CANT_BE_LOCAL_CLASS.on(declaration.nameIdentifier ?: declaration))
97+
}
98+
99+
val superTypes = TypeUtils.getAllSupertypes(descriptor.defaultType)
100+
if (superTypes.none { it.constructor.declarationDescriptor?.fqNameSafe == ANDROID_PARCELABLE_CLASS_FQNAME }) {
101+
diagnosticHolder.report(ErrorsAndroid.NO_PARCELABLE_SUPERTYPE.on(declaration.nameIdentifier ?: declaration))
102+
}
103+
104+
val primaryConstructor = declaration.primaryConstructor
105+
if (primaryConstructor == null && declaration.secondaryConstructors.isNotEmpty()) {
106+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR.on(declaration.nameIdentifier ?: declaration))
107+
}
108+
109+
for (parameter in primaryConstructor?.valueParameters.orEmpty()) {
110+
if (!parameter.hasValOrVar()) {
111+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(
112+
parameter.nameIdentifier ?: parameter))
113+
}
114+
}
115+
}
116+
}

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.intellij.mock.MockProject
2020
import com.intellij.openapi.extensions.Extensions
2121
import com.intellij.openapi.project.Project
2222
import org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension
23+
import org.jetbrains.kotlin.android.parcel.ParcelableDeclarationChecker
2324
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension
2425
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension
2526
import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidExtensionsExpressionCodegenExtension
@@ -118,6 +119,7 @@ class AndroidExtensionPropertiesComponentContainerContributor : StorageComponent
118119
override fun addDeclarations(container: StorageComponentContainer, platform: TargetPlatform) {
119120
if (platform is JvmPlatform) {
120121
container.useInstance(AndroidExtensionPropertiesCallChecker())
122+
container.useInstance(ParcelableDeclarationChecker())
121123
}
122124
}
123125
}

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
3939

4040
MAP.put(ErrorsAndroid.UNSAFE_CALL_ON_PARTIALLY_DEFINED_RESOURCE,
4141
"Potential NullPointerException. The resource is missing in some of layout versions")
42+
43+
MAP.put(ErrorsAndroid.PARCELABLE_SHOULD_BE_CLASS,
44+
"'Parcelable' should be a class")
45+
46+
MAP.put(ErrorsAndroid.PARCELABLE_SHOULD_BE_INSTANTIABLE,
47+
"'Parcelable' should not be a 'sealed' or 'abstract' class")
48+
49+
MAP.put(ErrorsAndroid.PARCELABLE_CANT_BE_INNER_CLASS,
50+
"'Parcelable' can't be an inner class")
51+
52+
MAP.put(ErrorsAndroid.PARCELABLE_CANT_BE_LOCAL_CLASS,
53+
"'Parcelable' can't be a local class")
54+
55+
MAP.put(ErrorsAndroid.NO_PARCELABLE_SUPERTYPE,
56+
"No 'Parcelable' supertype")
57+
58+
MAP.put(ErrorsAndroid.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR,
59+
"'Parcelable' should have a primary constructor")
60+
61+
MAP.put(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR,
62+
"'Parcelable' constructor parameter should be 'val' or 'var'")
63+
64+
MAP.put(ErrorsAndroid.PROPERTY_WONT_BE_SERIALIZED,
65+
"Property would not be serialized into a 'Parcel'. Add '@Transient' annotation to it")
4266
}
4367
}
4468

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
package org.jetbrains.kotlin.android.synthetic.diagnostic;
1818

19+
import com.intellij.psi.PsiElement;
1920
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
2021
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
2122
import org.jetbrains.kotlin.diagnostics.Errors;
2223
import org.jetbrains.kotlin.psi.KtExpression;
2324

25+
import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
2426
import static org.jetbrains.kotlin.diagnostics.Severity.WARNING;
2527

2628
public interface ErrorsAndroid {
@@ -29,6 +31,15 @@ public interface ErrorsAndroid {
2931
DiagnosticFactory0<KtExpression> SYNTHETIC_DEPRECATED_PACKAGE = DiagnosticFactory0.create(WARNING);
3032
DiagnosticFactory0<KtExpression> UNSAFE_CALL_ON_PARTIALLY_DEFINED_RESOURCE = DiagnosticFactory0.create(WARNING);
3133

34+
DiagnosticFactory0<PsiElement> PARCELABLE_SHOULD_BE_CLASS = DiagnosticFactory0.create(ERROR);
35+
DiagnosticFactory0<PsiElement> PARCELABLE_SHOULD_BE_INSTANTIABLE = DiagnosticFactory0.create(ERROR);
36+
DiagnosticFactory0<PsiElement> PARCELABLE_CANT_BE_INNER_CLASS = DiagnosticFactory0.create(ERROR);
37+
DiagnosticFactory0<PsiElement> PARCELABLE_CANT_BE_LOCAL_CLASS = DiagnosticFactory0.create(ERROR);
38+
DiagnosticFactory0<PsiElement> NO_PARCELABLE_SUPERTYPE = DiagnosticFactory0.create(ERROR);
39+
DiagnosticFactory0<PsiElement> PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR = DiagnosticFactory0.create(ERROR);
40+
DiagnosticFactory0<PsiElement> PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR = DiagnosticFactory0.create(ERROR);
41+
DiagnosticFactory0<PsiElement> PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING);
42+
3243
@SuppressWarnings("UnusedDeclaration")
3344
Object _initializer = new Object() {
3445
{
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package test
2+
3+
import kotlinx.android.parcel.MagicParcel
4+
import android.os.Parcelable
5+
6+
@MagicParcel
7+
class A : Parcelable
8+
9+
@MagicParcel
10+
class B(val firstName: String, val secondName: String) : Parcelable
11+
12+
@MagicParcel
13+
class C(val firstName: String, <error descr="[PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR] 'Parcelable' constructor parameter should be 'val' or 'var'">secondName</error>: String) : Parcelable
14+
15+
@MagicParcel
16+
class D(val firstName: String, vararg val secondName: String) : Parcelable
17+
18+
@MagicParcel
19+
class E(val firstName: String, val secondName: String) : Parcelable {
20+
constructor() : this("", "")
21+
}
22+
23+
@MagicParcel
24+
class <error descr="[PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR] 'Parcelable' should have a primary constructor">F</error> : Parcelable {
25+
constructor(a: String) {
26+
println(a)
27+
}
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package test
2+
3+
import kotlinx.android.parcel.MagicParcel
4+
import android.os.Parcelable
5+
6+
@MagicParcel
7+
open class Open(val foo: String) : Parcelable
8+
9+
@MagicParcel
10+
class Final(val foo: String) : Parcelable
11+
12+
@MagicParcel
13+
<error descr="[PARCELABLE_SHOULD_BE_INSTANTIABLE] 'Parcelable' should not be a 'sealed' or 'abstract' class">abstract</error> class Abstract(val foo: String) : Parcelable
14+
15+
@MagicParcel
16+
<error descr="[PARCELABLE_SHOULD_BE_INSTANTIABLE] 'Parcelable' should not be a 'sealed' or 'abstract' class">sealed</error> class Sealed(val foo: String) : Parcelable {
17+
class X : Sealed("")
18+
}
19+
20+
class Outer {
21+
@MagicParcel
22+
<error descr="[PARCELABLE_CANT_BE_INNER_CLASS] 'Parcelable' can't be an inner class">inner</error> class Inner(val foo: String) : Parcelable
23+
}
24+
25+
fun foo() {
26+
@MagicParcel
27+
<error descr="[PARCELABLE_SHOULD_BE_CLASS] 'Parcelable' should be a class">object</error> : Parcelable {}
28+
29+
@MagicParcel
30+
class <error descr="[NO_PARCELABLE_SUPERTYPE] No 'Parcelable' supertype"><error descr="[PARCELABLE_CANT_BE_LOCAL_CLASS] 'Parcelable' can't be a local class">Local</error></error> {}
31+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package test
2+
3+
import kotlinx.android.parcel.MagicParcel
4+
import android.os.Parcel
5+
import android.os.Parcelable
6+
7+
@Suppress("UNUSED_PARAMETER")
8+
class User(firstName: String, secondName: String, val age: Int) : Parcelable {
9+
override fun writeToParcel(p0: Parcel?, p1: Int) {}
10+
override fun describeContents() = 0
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package test
2+
3+
import kotlinx.android.parcel.MagicParcel
4+
import android.os.Parcelable
5+
6+
@MagicParcel
7+
class A(val firstName: String) : Parcelable {
8+
val <warning descr="[PROPERTY_WONT_BE_SERIALIZED] Property would not be serialized into a 'Parcel'. Add '@Transient' annotation to it">secondName</warning>: String = ""
9+
10+
val <warning descr="[PROPERTY_WONT_BE_SERIALIZED] Property would not be serialized into a 'Parcel'. Add '@Transient' annotation to it">delegated</warning> by lazy { "" }
11+
12+
lateinit var <warning descr="[PROPERTY_WONT_BE_SERIALIZED] Property would not be serialized into a 'Parcel'. Add '@Transient' annotation to it">lateinit</warning>: String
13+
14+
val customGetter: String
15+
get() = ""
16+
17+
var customSetter: String
18+
get() = ""
19+
set(v) {}
20+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package test
2+
3+
import kotlinx.android.parcel.MagicParcel
4+
import android.os.Parcelable
5+
6+
@MagicParcel
7+
class User(val firstName: String, val secondName: String, val age: Int) : Parcelable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package test
2+
3+
import kotlinx.android.parcel.MagicParcel
4+
import android.os.Parcelable
5+
6+
@MagicParcel
7+
class <error descr="[NO_PARCELABLE_SUPERTYPE] No 'Parcelable' supertype">Without</error>(val firstName: String, val secondName: String, val age: Int)
8+
9+
@MagicParcel
10+
class With(val firstName: String, val secondName: String, val age: Int) : Parcelable
11+
12+
interface MyParcelableIntf : Parcelable
13+
14+
abstract class MyParcelableCl : Parcelable
15+
16+
@MagicParcel
17+
class WithIntfSubtype(val firstName: String, val secondName: String, val age: Int) : MyParcelableIntf
18+
19+
@MagicParcel
20+
class WithClSubtype(val firstName: String, val secondName: String, val age: Int) : MyParcelableCl()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package test
2+
3+
import kotlinx.android.parcel.MagicParcel
4+
import android.os.Parcelable
5+
6+
@MagicParcel
7+
interface <error descr="[PARCELABLE_SHOULD_BE_CLASS] 'Parcelable' should be a class">Intf</error> : Parcelable
8+
9+
@MagicParcel
10+
object <error descr="[PARCELABLE_SHOULD_BE_CLASS] 'Parcelable' should be a class">Obj</error>
11+
12+
class A {
13+
@MagicParcel
14+
companion <error descr="[PARCELABLE_SHOULD_BE_CLASS] 'Parcelable' should be a class">object</error> {
15+
fun foo() {}
16+
}
17+
}
18+
19+
@MagicParcel
20+
enum class <error descr="[PARCELABLE_SHOULD_BE_CLASS] 'Parcelable' should be a class">Enum</error> {
21+
WHITE, BLACK
22+
}
23+
24+
@MagicParcel
25+
annotation class <error descr="[PARCELABLE_SHOULD_BE_CLASS] 'Parcelable' should be a class">Anno</error>

0 commit comments

Comments
 (0)