mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Generate proguard rules for code gen on demand (#1067)
* Extract target constructor signature into TargetConstructor.kt We'll need this to know what the runtime types are for proguard rules * Add ProguardConfig * Wire in proguard config * Write proguard configs out with adapters * Add full tests * Now remove the rules! * Ignore on inline classes for now * Pass adapter constructor params correctly
This commit is contained in:
@@ -47,6 +47,11 @@
|
||||
<artifactId>kotlinpoet-classinspector-elements</artifactId>
|
||||
<version>${kotlinpoet.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>${asm.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.ltgt.gradle.incap</groupId>
|
||||
<artifactId>incap</artifactId>
|
||||
|
@@ -108,9 +108,9 @@ class JsonClassCodegenProcessor : AbstractProcessor() {
|
||||
val jsonClass = type.getAnnotation(annotation)
|
||||
if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
|
||||
val generator = adapterGenerator(type, cachedClassInspector) ?: continue
|
||||
generator
|
||||
.generateFile {
|
||||
it.toBuilder()
|
||||
val preparedAdapter = generator
|
||||
.prepare { spec ->
|
||||
spec.toBuilder()
|
||||
.apply {
|
||||
generatedType?.asClassName()?.let { generatedClassName ->
|
||||
addAnnotation(
|
||||
@@ -125,14 +125,19 @@ class JsonClassCodegenProcessor : AbstractProcessor() {
|
||||
.addOriginatingElement(type)
|
||||
.build()
|
||||
}
|
||||
.writeTo(filer)
|
||||
|
||||
preparedAdapter.spec.writeTo(filer)
|
||||
preparedAdapter.proguardConfig?.writeTo(filer, type)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun adapterGenerator(element: TypeElement, cachedClassInspector: MoshiCachedClassInspector): AdapterGenerator? {
|
||||
private fun adapterGenerator(
|
||||
element: TypeElement,
|
||||
cachedClassInspector: MoshiCachedClassInspector
|
||||
): AdapterGenerator? {
|
||||
val type = targetType(messager, elements, types, element, cachedClassInspector) ?: return null
|
||||
|
||||
val properties = mutableMapOf<String, PropertyGenerator>()
|
||||
|
@@ -45,6 +45,7 @@ import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty
|
||||
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.Type
|
||||
import org.objectweb.asm.Type as AsmType
|
||||
|
||||
private val MOSHI_UTIL = Util::class.asClassName()
|
||||
private const val TO_STRING_PREFIX = "GeneratedJsonAdapter("
|
||||
@@ -52,7 +53,7 @@ private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the
|
||||
|
||||
/** Generates a JSON adapter for a target type. */
|
||||
internal class AdapterGenerator(
|
||||
target: TargetType,
|
||||
private val target: TargetType,
|
||||
private val propertyList: List<PropertyGenerator>
|
||||
) {
|
||||
|
||||
@@ -60,6 +61,8 @@ internal class AdapterGenerator(
|
||||
private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT)
|
||||
private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of(
|
||||
"%T.DEFAULT_CONSTRUCTOR_MARKER", Util::class)
|
||||
private val CN_MOSHI = Moshi::class.asClassName()
|
||||
private val CN_TYPE = Type::class.asClassName()
|
||||
|
||||
private val COMMON_SUPPRESS = arrayOf(
|
||||
// https://github.com/square/moshi/issues/1023
|
||||
@@ -98,10 +101,10 @@ internal class AdapterGenerator(
|
||||
|
||||
private val moshiParam = ParameterSpec.builder(
|
||||
nameAllocator.newName("moshi"),
|
||||
Moshi::class).build()
|
||||
CN_MOSHI).build()
|
||||
private val typesParam = ParameterSpec.builder(
|
||||
nameAllocator.newName("types"),
|
||||
ARRAY.parameterizedBy(Type::class.asTypeName()))
|
||||
ARRAY.parameterizedBy(CN_TYPE))
|
||||
.build()
|
||||
private val readerParam = ParameterSpec.builder(
|
||||
nameAllocator.newName("reader"),
|
||||
@@ -140,15 +143,59 @@ internal class AdapterGenerator(
|
||||
.initializer("null")
|
||||
.build()
|
||||
|
||||
fun generateFile(typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec {
|
||||
fun prepare(typeHook: (TypeSpec) -> TypeSpec = { it }): PreparedAdapter {
|
||||
for (property in nonTransientProperties) {
|
||||
property.allocateNames(nameAllocator)
|
||||
}
|
||||
|
||||
val generatedAdapter = generateType().let(typeHook)
|
||||
val result = FileSpec.builder(className.packageName, adapterName)
|
||||
result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
|
||||
result.addType(generateType().let(typeHook))
|
||||
return result.build()
|
||||
result.addType(generatedAdapter)
|
||||
return PreparedAdapter(result.build(), generatedAdapter.createProguardRule())
|
||||
}
|
||||
|
||||
private fun TypeSpec.createProguardRule(): ProguardConfig {
|
||||
val adapterProperties = propertySpecs
|
||||
.asSequence()
|
||||
.filter { prop ->
|
||||
prop.type.rawType() == JsonAdapter::class.asClassName()
|
||||
}
|
||||
.filter { prop -> prop.annotations.isNotEmpty() }
|
||||
.mapTo(mutableSetOf()) { prop ->
|
||||
QualifierAdapterProperty(
|
||||
name = prop.name,
|
||||
qualifiers = prop.annotations.mapTo(mutableSetOf()) { it.className }
|
||||
)
|
||||
}
|
||||
|
||||
val adapterConstructorParams = when (requireNotNull(primaryConstructor).parameters.size) {
|
||||
1 -> listOf(CN_MOSHI.canonicalName)
|
||||
2 -> listOf(CN_MOSHI.canonicalName, "${CN_TYPE.canonicalName}[]")
|
||||
// Should never happen
|
||||
else -> error("Unexpected number of arguments on primary constructor: $primaryConstructor")
|
||||
}
|
||||
|
||||
var hasDefaultProperties = false
|
||||
var parameterTypes = emptyList<String>()
|
||||
target.constructor.signature?.let { constructorSignature ->
|
||||
if (constructorSignature.startsWith("constructor-impl")) {
|
||||
// Inline class, we don't support this yet.
|
||||
// This is a static method with signature like 'constructor-impl(I)I'
|
||||
return@let
|
||||
}
|
||||
hasDefaultProperties = propertyList.any { it.hasDefault }
|
||||
parameterTypes = AsmType.getArgumentTypes(constructorSignature.removePrefix("<init>"))
|
||||
.map { it.toCanonicalString() }
|
||||
}
|
||||
return ProguardConfig(
|
||||
targetClass = className,
|
||||
adapterName = adapterName,
|
||||
adapterConstructorParams = adapterConstructorParams,
|
||||
targetConstructorHasDefaults = hasDefaultProperties,
|
||||
targetConstructorParams = parameterTypes,
|
||||
qualifierProperties = adapterProperties
|
||||
)
|
||||
}
|
||||
|
||||
private fun generateType(): TypeSpec {
|
||||
@@ -518,6 +565,28 @@ internal class AdapterGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a prepared adapter with its [spec] and optional associated [proguardConfig]. */
|
||||
internal data class PreparedAdapter(val spec: FileSpec, val proguardConfig: ProguardConfig?)
|
||||
|
||||
private fun AsmType.toCanonicalString(): String {
|
||||
return when (this) {
|
||||
AsmType.VOID_TYPE -> "void"
|
||||
AsmType.BOOLEAN_TYPE -> "boolean"
|
||||
AsmType.CHAR_TYPE -> "char"
|
||||
AsmType.BYTE_TYPE -> "byte"
|
||||
AsmType.SHORT_TYPE -> "short"
|
||||
AsmType.INT_TYPE -> "int"
|
||||
AsmType.FLOAT_TYPE -> "float"
|
||||
AsmType.LONG_TYPE -> "long"
|
||||
AsmType.DOUBLE_TYPE -> "double"
|
||||
else -> when (sort) {
|
||||
AsmType.ARRAY -> "${elementType.toCanonicalString()}[]"
|
||||
// Object type
|
||||
else -> className
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface PropertyComponent {
|
||||
val property: PropertyGenerator
|
||||
val type: TypeName
|
||||
|
@@ -0,0 +1,104 @@
|
||||
package com.squareup.moshi.kotlin.codegen.api
|
||||
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import javax.annotation.processing.Filer
|
||||
import javax.lang.model.element.Element
|
||||
import javax.tools.StandardLocation
|
||||
|
||||
/**
|
||||
* Represents a proguard configuration for a given spec. This covers three main areas:
|
||||
* - Keeping the target class name to Moshi's reflective lookup of the adapter.
|
||||
* - Keeping the generated adapter class name + public constructor for reflective lookup.
|
||||
* - Keeping any used JsonQualifier annotations and the properties they are attached to.
|
||||
* - If the target class has default parameter values, also keeping the associated synthetic
|
||||
* constructor as well as the DefaultConstructorMarker type Kotlin adds to it.
|
||||
*
|
||||
* Each rule is intended to be as specific and targeted as possible to reduce footprint, and each is
|
||||
* conditioned on usage of the original target type.
|
||||
*
|
||||
* To keep this processor as an ISOLATING incremental processor, we generate one file per target
|
||||
* class with a deterministic name (see [outputFile]) with an appropriate originating element.
|
||||
*/
|
||||
internal data class ProguardConfig(
|
||||
val targetClass: ClassName,
|
||||
val adapterName: String,
|
||||
val adapterConstructorParams: List<String>,
|
||||
val targetConstructorHasDefaults: Boolean,
|
||||
val targetConstructorParams: List<String>,
|
||||
val qualifierProperties: Set<QualifierAdapterProperty>
|
||||
) {
|
||||
private val outputFile = "META-INF/proguard/moshi-${targetClass.canonicalName}.pro"
|
||||
|
||||
/** Writes this to `filer`. */
|
||||
fun writeTo(filer: Filer, vararg originatingElements: Element) {
|
||||
filer.createResource(StandardLocation.CLASS_OUTPUT, "", outputFile, *originatingElements)
|
||||
.openWriter()
|
||||
.use(::writeTo)
|
||||
}
|
||||
|
||||
private fun writeTo(out: Appendable): Unit = out.run {
|
||||
//
|
||||
// -if class {the target class}
|
||||
// -keepnames class {the target class}
|
||||
// -if class {the target class}
|
||||
// -keep class {the generated adapter} {
|
||||
// <init>(...);
|
||||
// private final {adapter fields}
|
||||
// }
|
||||
//
|
||||
val targetName = targetClass.canonicalName
|
||||
val adapterCanonicalName = ClassName(targetClass.packageName, adapterName)
|
||||
// Keep the class name for Moshi's reflective lookup based on it
|
||||
appendln("-if class $targetName")
|
||||
appendln("-keepnames class $targetName")
|
||||
|
||||
appendln("-if class $targetName")
|
||||
appendln("-keep class $adapterCanonicalName {")
|
||||
// Keep the constructor for Moshi's reflective lookup
|
||||
val constructorArgs = adapterConstructorParams.joinToString(",")
|
||||
appendln(" public <init>($constructorArgs)")
|
||||
// Keep any qualifier properties
|
||||
for (qualifierProperty in qualifierProperties) {
|
||||
appendln(" private com.squareup.moshi.JsonAdapter ${qualifierProperty.name}")
|
||||
}
|
||||
appendln("}")
|
||||
|
||||
qualifierProperties.asSequence()
|
||||
.flatMap { it.qualifiers.asSequence() }
|
||||
.map(ClassName::canonicalName)
|
||||
.sorted()
|
||||
.forEach { qualifier ->
|
||||
appendln("-if class $targetName")
|
||||
appendln("-keep @interface $qualifier")
|
||||
}
|
||||
|
||||
if (targetConstructorHasDefaults) {
|
||||
// If the target class has default parameter values, keep its synthetic constructor
|
||||
//
|
||||
// -keepnames class kotlin.jvm.internal.DefaultConstructorMarker
|
||||
// -keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * {
|
||||
// synthetic <init>(...);
|
||||
// }
|
||||
//
|
||||
appendln("-if class $targetName")
|
||||
appendln("-keepnames class kotlin.jvm.internal.DefaultConstructorMarker")
|
||||
appendln("-if class $targetName")
|
||||
appendln("-keepclassmembers class ${targetClass.canonicalName} {")
|
||||
val allParams = targetConstructorParams.toMutableList()
|
||||
val maskCount = (targetConstructorParams.size % 32) + 1
|
||||
repeat(maskCount) {
|
||||
allParams += "int"
|
||||
}
|
||||
allParams += "kotlin.jvm.internal.DefaultConstructorMarker"
|
||||
val params = allParams.joinToString(",")
|
||||
appendln(" public synthetic <init>($params)")
|
||||
appendln("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a qualified property with its [name] in the adapter fields and list of [qualifiers]
|
||||
* associated with it.
|
||||
*/
|
||||
internal data class QualifierAdapterProperty(val name: String, val qualifiers: Set<ClassName>)
|
@@ -20,7 +20,8 @@ import com.squareup.kotlinpoet.KModifier
|
||||
/** A constructor in user code that should be called by generated code. */
|
||||
internal data class TargetConstructor(
|
||||
val parameters: LinkedHashMap<String, TargetParameter>,
|
||||
val visibility: KModifier
|
||||
val visibility: KModifier,
|
||||
val signature: String?
|
||||
) {
|
||||
init {
|
||||
visibility.checkIsVisibility()
|
||||
|
@@ -22,6 +22,7 @@ import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.asClassName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import com.squareup.kotlinpoet.metadata.ImmutableKmConstructor
|
||||
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
|
||||
import com.squareup.kotlinpoet.metadata.isAbstract
|
||||
import com.squareup.kotlinpoet.metadata.isClass
|
||||
@@ -54,7 +55,7 @@ import javax.lang.model.element.ElementKind
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.util.Elements
|
||||
import javax.lang.model.util.Types
|
||||
import javax.tools.Diagnostic
|
||||
import javax.tools.Diagnostic.Kind.ERROR
|
||||
|
||||
private val JSON_QUALIFIER = JsonQualifier::class.java
|
||||
private val JSON = Json::class.asClassName()
|
||||
@@ -71,7 +72,12 @@ private fun Collection<KModifier>.visibility(): KModifier {
|
||||
}
|
||||
|
||||
@KotlinPoetMetadataPreview
|
||||
internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): TargetConstructor? {
|
||||
internal fun primaryConstructor(
|
||||
targetElement: TypeElement,
|
||||
kotlinApi: TypeSpec,
|
||||
elements: Elements,
|
||||
messager: Messager
|
||||
): TargetConstructor? {
|
||||
val primaryConstructor = kotlinApi.primaryConstructor ?: return null
|
||||
|
||||
val parameters = LinkedHashMap<String, TargetParameter>()
|
||||
@@ -87,7 +93,14 @@ internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): Target
|
||||
)
|
||||
}
|
||||
|
||||
return TargetConstructor(parameters, primaryConstructor.modifiers.visibility())
|
||||
val kmConstructorSignature = primaryConstructor.tag<ImmutableKmConstructor>()?.signature?.toString()
|
||||
?: run {
|
||||
messager.printMessage(ERROR, "No KmConstructor found for primary constructor.",
|
||||
targetElement)
|
||||
null
|
||||
}
|
||||
return TargetConstructor(parameters, primaryConstructor.modifiers.visibility(),
|
||||
kmConstructorSignature)
|
||||
}
|
||||
|
||||
/** Returns a target type for `element`, or null if it cannot be used with code gen. */
|
||||
@@ -101,7 +114,7 @@ internal fun targetType(messager: Messager,
|
||||
val typeMetadata = element.getAnnotation(Metadata::class.java)
|
||||
if (typeMetadata == null) {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
@@ -110,7 +123,7 @@ internal fun targetType(messager: Messager,
|
||||
cachedClassInspector.toImmutableKmClass(typeMetadata)
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Class type",
|
||||
ERROR, "@JsonClass can't be applied to $element: must be a Class type",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
@@ -118,44 +131,44 @@ internal fun targetType(messager: Messager,
|
||||
when {
|
||||
kmClass.isEnum -> {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
ERROR,
|
||||
"@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
!kmClass.isClass -> {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
kmClass.isInner -> {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must not be an inner class", element)
|
||||
return null
|
||||
}
|
||||
kmClass.isSealed -> {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be sealed",
|
||||
ERROR, "@JsonClass can't be applied to $element: must not be sealed",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
kmClass.isAbstract -> {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be abstract",
|
||||
ERROR, "@JsonClass can't be applied to $element: must not be abstract",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
kmClass.isLocal -> {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be local",
|
||||
ERROR, "@JsonClass can't be applied to $element: must not be local",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
!kmClass.isPublic && !kmClass.isInternal -> {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must be internal or public",
|
||||
element)
|
||||
return null
|
||||
@@ -166,14 +179,14 @@ internal fun targetType(messager: Messager,
|
||||
val typeVariables = kotlinApi.typeVariables
|
||||
val appliedType = AppliedType.get(element)
|
||||
|
||||
val constructor = primaryConstructor(kotlinApi, elements)
|
||||
val constructor = primaryConstructor(element, kotlinApi, elements, messager)
|
||||
if (constructor == null) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR, "No primary constructor found on $element",
|
||||
messager.printMessage(ERROR, "No primary constructor found on $element",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: " +
|
||||
messager.printMessage(ERROR, "@JsonClass can't be applied to $element: " +
|
||||
"primary constructor is not internal or public", element)
|
||||
return null
|
||||
}
|
||||
@@ -186,7 +199,7 @@ internal fun targetType(messager: Messager,
|
||||
}
|
||||
.onEach { supertype ->
|
||||
if (supertype.element.getAnnotation(Metadata::class.java) == null) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR,
|
||||
messager.printMessage(ERROR,
|
||||
"@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type",
|
||||
element)
|
||||
return null
|
||||
@@ -275,7 +288,7 @@ internal fun TargetProperty.generator(
|
||||
if (isTransient) {
|
||||
if (!hasDefault) {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR, "No default value for transient property $name",
|
||||
ERROR, "No default value for transient property $name",
|
||||
sourceElement)
|
||||
return null
|
||||
}
|
||||
@@ -283,7 +296,7 @@ internal fun TargetProperty.generator(
|
||||
}
|
||||
|
||||
if (!isVisible) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR, "property $name is not visible",
|
||||
messager.printMessage(ERROR, "property $name is not visible",
|
||||
sourceElement)
|
||||
return null
|
||||
}
|
||||
@@ -300,13 +313,13 @@ internal fun TargetProperty.generator(
|
||||
?: continue
|
||||
annotationElement.getAnnotation(Retention::class.java)?.let {
|
||||
if (it.value != RetentionPolicy.RUNTIME) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR,
|
||||
messager.printMessage(ERROR,
|
||||
"JsonQualifier @${jsonQualifier.className.simpleName} must have RUNTIME retention")
|
||||
}
|
||||
}
|
||||
annotationElement.getAnnotation(Target::class.java)?.let {
|
||||
if (ElementType.FIELD !in it.value) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR,
|
||||
messager.printMessage(ERROR,
|
||||
"JsonQualifier @${jsonQualifier.className.simpleName} must support FIELD target")
|
||||
}
|
||||
}
|
||||
@@ -335,7 +348,9 @@ private fun List<AnnotationSpec>?.jsonName(): String? {
|
||||
val mirror = requireNotNull(annotation.tag<AnnotationMirror>()) {
|
||||
"Could not get the annotation mirror from the annotation spec"
|
||||
}
|
||||
mirror.elementValues.entries.single { it.key.simpleName.contentEquals("name") }.value.value as String
|
||||
mirror.elementValues.entries.single {
|
||||
it.key.simpleName.contentEquals("name")
|
||||
}.value.value as String
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -398,6 +398,211 @@ class JsonClassCodegenProcessorTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Processor should generate comprehensive proguard rules`() {
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
package testPackage
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
|
||||
typealias FirstName = String
|
||||
typealias LastName = String
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Aliases(val firstName: FirstName, val lastName: LastName, val hairColor: String)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Simple(val firstName: String)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Generic<T>(val firstName: T, val lastName: String)
|
||||
|
||||
@JsonQualifier
|
||||
annotation class MyQualifier
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UsingQualifiers(val firstName: String, @MyQualifier val lastName: String)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MixedTypes(val firstName: String, val otherNames: MutableList<String>)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DefaultParams(val firstName: String = "")
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Complex<T>(val firstName: FirstName = "", @MyQualifier val names: MutableList<String>, val genericProp: T)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class MultipleMasks(
|
||||
val arg0: Long = 0,
|
||||
val arg1: Long = 1,
|
||||
val arg2: Long = 2,
|
||||
val arg3: Long = 3,
|
||||
val arg4: Long = 4,
|
||||
val arg5: Long = 5,
|
||||
val arg6: Long = 6,
|
||||
val arg7: Long = 7,
|
||||
val arg8: Long = 8,
|
||||
val arg9: Long = 9,
|
||||
val arg10: Long = 10,
|
||||
val arg11: Long,
|
||||
val arg12: Long = 12,
|
||||
val arg13: Long = 13,
|
||||
val arg14: Long = 14,
|
||||
val arg15: Long = 15,
|
||||
val arg16: Long = 16,
|
||||
val arg17: Long = 17,
|
||||
val arg18: Long = 18,
|
||||
val arg19: Long = 19,
|
||||
@Suppress("UNUSED_PARAMETER") arg20: Long = 20,
|
||||
val arg21: Long = 21,
|
||||
val arg22: Long = 22,
|
||||
val arg23: Long = 23,
|
||||
val arg24: Long = 24,
|
||||
val arg25: Long = 25,
|
||||
val arg26: Long = 26,
|
||||
val arg27: Long = 27,
|
||||
val arg28: Long = 28,
|
||||
val arg29: Long = 29,
|
||||
val arg30: Long = 30,
|
||||
val arg31: Long = 31,
|
||||
val arg32: Long = 32,
|
||||
val arg33: Long = 33,
|
||||
val arg34: Long = 34,
|
||||
val arg35: Long = 35,
|
||||
val arg36: Long = 36,
|
||||
val arg37: Long = 37,
|
||||
val arg38: Long = 38,
|
||||
@Transient val arg39: Long = 39,
|
||||
val arg40: Long = 40,
|
||||
val arg41: Long = 41,
|
||||
val arg42: Long = 42,
|
||||
val arg43: Long = 43,
|
||||
val arg44: Long = 44,
|
||||
val arg45: Long = 45,
|
||||
val arg46: Long = 46,
|
||||
val arg47: Long = 47,
|
||||
val arg48: Long = 48,
|
||||
val arg49: Long = 49,
|
||||
val arg50: Long = 50,
|
||||
val arg51: Long = 51,
|
||||
val arg52: Long = 52,
|
||||
@Transient val arg53: Long = 53,
|
||||
val arg54: Long = 54,
|
||||
val arg55: Long = 55,
|
||||
val arg56: Long = 56,
|
||||
val arg57: Long = 57,
|
||||
val arg58: Long = 58,
|
||||
val arg59: Long = 59,
|
||||
val arg60: Long = 60,
|
||||
val arg61: Long = 61,
|
||||
val arg62: Long = 62,
|
||||
val arg63: Long = 63,
|
||||
val arg64: Long = 64,
|
||||
val arg65: Long = 65
|
||||
)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Aliases.pro" }).hasContent("""
|
||||
-if class testPackage.Aliases
|
||||
-keepnames class testPackage.Aliases
|
||||
-if class testPackage.Aliases
|
||||
-keep class testPackage.AliasesJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi)
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Simple.pro" }).hasContent("""
|
||||
-if class testPackage.Simple
|
||||
-keepnames class testPackage.Simple
|
||||
-if class testPackage.Simple
|
||||
-keep class testPackage.SimpleJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi)
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Generic.pro" }).hasContent("""
|
||||
-if class testPackage.Generic
|
||||
-keepnames class testPackage.Generic
|
||||
-if class testPackage.Generic
|
||||
-keep class testPackage.GenericJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[])
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.UsingQualifiers.pro" }).hasContent("""
|
||||
-if class testPackage.UsingQualifiers
|
||||
-keepnames class testPackage.UsingQualifiers
|
||||
-if class testPackage.UsingQualifiers
|
||||
-keep class testPackage.UsingQualifiersJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi)
|
||||
private com.squareup.moshi.JsonAdapter stringAtMyQualifierAdapter
|
||||
}
|
||||
-if class testPackage.UsingQualifiers
|
||||
-keep @interface testPackage.MyQualifier
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.MixedTypes.pro" }).hasContent("""
|
||||
-if class testPackage.MixedTypes
|
||||
-keepnames class testPackage.MixedTypes
|
||||
-if class testPackage.MixedTypes
|
||||
-keep class testPackage.MixedTypesJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi)
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.DefaultParams.pro" }).hasContent("""
|
||||
-if class testPackage.DefaultParams
|
||||
-keepnames class testPackage.DefaultParams
|
||||
-if class testPackage.DefaultParams
|
||||
-keep class testPackage.DefaultParamsJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi)
|
||||
}
|
||||
-if class testPackage.DefaultParams
|
||||
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
|
||||
-if class testPackage.DefaultParams
|
||||
-keepclassmembers class testPackage.DefaultParams {
|
||||
public synthetic <init>(java.lang.String,int,int,kotlin.jvm.internal.DefaultConstructorMarker)
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Complex.pro" }).hasContent("""
|
||||
-if class testPackage.Complex
|
||||
-keepnames class testPackage.Complex
|
||||
-if class testPackage.Complex
|
||||
-keep class testPackage.ComplexJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[])
|
||||
private com.squareup.moshi.JsonAdapter mutableListOfStringAtMyQualifierAdapter
|
||||
}
|
||||
-if class testPackage.Complex
|
||||
-keep @interface testPackage.MyQualifier
|
||||
-if class testPackage.Complex
|
||||
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
|
||||
-if class testPackage.Complex
|
||||
-keepclassmembers class testPackage.Complex {
|
||||
public synthetic <init>(java.lang.String,java.util.List,java.lang.Object,int,int,int,int,kotlin.jvm.internal.DefaultConstructorMarker)
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.MultipleMasks.pro" }).hasContent("""
|
||||
-if class testPackage.MultipleMasks
|
||||
-keepnames class testPackage.MultipleMasks
|
||||
-if class testPackage.MultipleMasks
|
||||
-keep class testPackage.MultipleMasksJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi)
|
||||
}
|
||||
-if class testPackage.MultipleMasks
|
||||
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
|
||||
-if class testPackage.MultipleMasks
|
||||
-keepclassmembers class testPackage.MultipleMasks {
|
||||
public synthetic <init>(long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,int,int,int,kotlin.jvm.internal.DefaultConstructorMarker)
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation {
|
||||
return KotlinCompilation()
|
||||
.apply {
|
||||
|
Reference in New Issue
Block a user