1 /*
<lambda>null2  * Copyright (C) 2018 Square, Inc.
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  *    https://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 package com.squareup.moshi.kotlin.codegen.apt
17 
18 import com.google.common.truth.Truth.assertThat
19 import com.squareup.moshi.JsonAdapter
20 import com.squareup.moshi.JsonReader
21 import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATED
22 import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATE_PROGUARD_RULES
23 import com.tschuchort.compiletesting.KotlinCompilation
24 import com.tschuchort.compiletesting.SourceFile
25 import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
26 import org.junit.Ignore
27 import org.junit.Rule
28 import org.junit.Test
29 import org.junit.rules.TemporaryFolder
30 import kotlin.reflect.KClass
31 import kotlin.reflect.KClassifier
32 import kotlin.reflect.KType
33 import kotlin.reflect.KTypeProjection
34 import kotlin.reflect.KVariance
35 import kotlin.reflect.KVariance.INVARIANT
36 import kotlin.reflect.full.createType
37 import kotlin.reflect.full.declaredMemberProperties
38 
39 /** Execute kotlinc to confirm that either files are generated or errors are printed. */
40 class JsonClassCodegenProcessorTest {
41 
42   @Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
43 
44   @Test
45   fun privateConstructor() {
46     val result = compile(
47       kotlin(
48         "source.kt",
49         """
50           import com.squareup.moshi.JsonClass
51 
52           @JsonClass(generateAdapter = true)
53           class PrivateConstructor private constructor(val a: Int, val b: Int) {
54             fun a() = a
55             fun b() = b
56             companion object {
57               fun newInstance(a: Int, b: Int) = PrivateConstructor(a, b)
58             }
59           }
60           """
61       )
62     )
63     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
64     assertThat(result.messages).contains("constructor is not internal or public")
65   }
66 
67   @Test
68   fun privateConstructorParameter() {
69     val result = compile(
70       kotlin(
71         "source.kt",
72         """
73           import com.squareup.moshi.JsonClass
74 
75           @JsonClass(generateAdapter = true)
76           class PrivateConstructorParameter(private var a: Int)
77           """
78       )
79     )
80     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
81     assertThat(result.messages).contains("property a is not visible")
82   }
83 
84   @Test
85   fun privateProperties() {
86     val result = compile(
87       kotlin(
88         "source.kt",
89         """
90           import com.squareup.moshi.JsonClass
91 
92           @JsonClass(generateAdapter = true)
93           class PrivateProperties {
94             private var a: Int = -1
95             private var b: Int = -1
96           }
97           """
98       )
99     )
100     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
101     assertThat(result.messages).contains("property a is not visible")
102   }
103 
104   @Test
105   fun interfacesNotSupported() {
106     val result = compile(
107       kotlin(
108         "source.kt",
109         """
110           import com.squareup.moshi.JsonClass
111 
112           @JsonClass(generateAdapter = true)
113           interface Interface
114           """
115       )
116     )
117     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
118     assertThat(result.messages).contains(
119       "error: @JsonClass can't be applied to Interface: must be a Kotlin class"
120     )
121   }
122 
123   @Test
124   fun interfacesDoNotErrorWhenGeneratorNotSet() {
125     val result = compile(
126       kotlin(
127         "source.kt",
128         """
129           import com.squareup.moshi.JsonClass
130 
131           @JsonClass(generateAdapter = true, generator="customGenerator")
132           interface Interface
133           """
134       )
135     )
136     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
137   }
138 
139   @Test
140   fun abstractClassesNotSupported() {
141     val result = compile(
142       kotlin(
143         "source.kt",
144         """
145           import com.squareup.moshi.JsonClass
146 
147           @JsonClass(generateAdapter = true)
148           abstract class AbstractClass(val a: Int)
149           """
150       )
151     )
152     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
153     assertThat(result.messages).contains(
154       "error: @JsonClass can't be applied to AbstractClass: must not be abstract"
155     )
156   }
157 
158   @Test
159   fun sealedClassesNotSupported() {
160     val result = compile(
161       kotlin(
162         "source.kt",
163         """
164           import com.squareup.moshi.JsonClass
165 
166           @JsonClass(generateAdapter = true)
167           sealed class SealedClass(val a: Int)
168           """
169       )
170     )
171     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
172     assertThat(result.messages).contains(
173       "error: @JsonClass can't be applied to SealedClass: must not be sealed"
174     )
175   }
176 
177   @Test
178   fun innerClassesNotSupported() {
179     val result = compile(
180       kotlin(
181         "source.kt",
182         """
183           import com.squareup.moshi.JsonClass
184 
185           class Outer {
186             @JsonClass(generateAdapter = true)
187             inner class InnerClass(val a: Int)
188           }
189           """
190       )
191     )
192     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
193     assertThat(result.messages).contains(
194       "error: @JsonClass can't be applied to Outer.InnerClass: must not be an inner class"
195     )
196   }
197 
198   @Test
199   fun enumClassesNotSupported() {
200     val result = compile(
201       kotlin(
202         "source.kt",
203         """
204           import com.squareup.moshi.JsonClass
205 
206           @JsonClass(generateAdapter = true)
207           enum class KotlinEnum {
208             A, B
209           }
210           """
211       )
212     )
213     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
214     assertThat(result.messages).contains(
215       "error: @JsonClass with 'generateAdapter = \"true\"' can't be applied to KotlinEnum: code gen for enums is not supported or necessary"
216     )
217   }
218 
219   // Annotation processors don't get called for local classes, so we don't have the opportunity to
220   @Ignore
221   @Test
222   fun localClassesNotSupported() {
223     val result = compile(
224       kotlin(
225         "source.kt",
226         """
227           import com.squareup.moshi.JsonClass
228 
229           fun outer() {
230             @JsonClass(generateAdapter = true)
231             class LocalClass(val a: Int)
232           }
233           """
234       )
235     )
236     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
237     assertThat(result.messages).contains(
238       "error: @JsonClass can't be applied to LocalClass: must not be local"
239     )
240   }
241 
242   @Test
243   fun privateClassesNotSupported() {
244     val result = compile(
245       kotlin(
246         "source.kt",
247         """
248           import com.squareup.moshi.JsonClass
249 
250           @JsonClass(generateAdapter = true)
251           private class PrivateClass(val a: Int)
252           """
253       )
254     )
255     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
256     assertThat(result.messages).contains(
257       "error: @JsonClass can't be applied to PrivateClass: must be internal or public"
258     )
259   }
260 
261   @Test
262   fun objectDeclarationsNotSupported() {
263     val result = compile(
264       kotlin(
265         "source.kt",
266         """
267           import com.squareup.moshi.JsonClass
268 
269           @JsonClass(generateAdapter = true)
270           object ObjectDeclaration {
271             var a = 5
272           }
273           """
274       )
275     )
276     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
277     assertThat(result.messages).contains(
278       "error: @JsonClass can't be applied to ObjectDeclaration: must be a Kotlin class"
279     )
280   }
281 
282   @Test
283   fun objectExpressionsNotSupported() {
284     val result = compile(
285       kotlin(
286         "source.kt",
287         """
288           import com.squareup.moshi.JsonClass
289 
290           @JsonClass(generateAdapter = true)
291           val expression = object : Any() {
292             var a = 5
293           }
294           """
295       )
296     )
297     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
298     assertThat(result.messages).contains(
299       "error: @JsonClass can't be applied to getExpression\$annotations(): must be a Kotlin class"
300     )
301   }
302 
303   @Test
304   fun requiredTransientConstructorParameterFails() {
305     val result = compile(
306       kotlin(
307         "source.kt",
308         """
309           import com.squareup.moshi.JsonClass
310 
311           @JsonClass(generateAdapter = true)
312           class RequiredTransientConstructorParameter(@Transient var a: Int)
313           """
314       )
315     )
316     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
317     assertThat(result.messages).contains(
318       "error: No default value for transient/ignored property a"
319     )
320   }
321 
322   @Test
323   fun requiredIgnoredConstructorParameterFails() {
324     val result = compile(
325       kotlin(
326         "source.kt",
327         """
328           import com.squareup.moshi.Json
329           import com.squareup.moshi.JsonClass
330 
331           @JsonClass(generateAdapter = true)
332           class RequiredIgnoredConstructorParameter(@Json(ignore = true) var a: Int)
333           """
334       )
335     )
336     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
337     assertThat(result.messages).contains(
338       "error: No default value for transient/ignored property a"
339     )
340   }
341 
342   @Test
343   fun nonPropertyConstructorParameter() {
344     val result = compile(
345       kotlin(
346         "source.kt",
347         """
348           import com.squareup.moshi.JsonClass
349 
350           @JsonClass(generateAdapter = true)
351           class NonPropertyConstructorParameter(a: Int, val b: Int)
352           """
353       )
354     )
355     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
356     assertThat(result.messages).contains(
357       "error: No property for required constructor parameter a"
358     )
359   }
360 
361   @Test
362   fun badGeneratedAnnotation() {
363     val result = prepareCompilation(
364       kotlin(
365         "source.kt",
366         """
367           import com.squareup.moshi.JsonClass
368 
369           @JsonClass(generateAdapter = true)
370           data class Foo(val a: Int)
371           """
372       )
373     ).apply {
374       kaptArgs[OPTION_GENERATED] = "javax.annotation.GeneratedBlerg"
375     }.compile()
376     assertThat(result.messages).contains(
377       "Invalid option value for $OPTION_GENERATED"
378     )
379   }
380   @Test
381   fun disableProguardRulesGenerating() {
382     val result = prepareCompilation(
383       kotlin(
384         "source.kt",
385         """
386           import com.squareup.moshi.JsonClass
387 
388           @JsonClass(generateAdapter = true)
389           data class Foo(val a: Int)
390           """
391       )
392     ).apply {
393       kaptArgs[OPTION_GENERATE_PROGUARD_RULES] = "false"
394     }.compile()
395     assertThat(result.generatedFiles.filter { it.endsWith(".pro") }).isEmpty()
396   }
397 
398   @Test
399   fun multipleErrors() {
400     val result = compile(
401       kotlin(
402         "source.kt",
403         """
404           import com.squareup.moshi.JsonClass
405 
406           @JsonClass(generateAdapter = true)
407           class Class1(private var a: Int, private var b: Int)
408 
409           @JsonClass(generateAdapter = true)
410           class Class2(private var c: Int)
411           """
412       )
413     )
414     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
415     assertThat(result.messages).contains("property a is not visible")
416     assertThat(result.messages).contains("property c is not visible")
417   }
418 
419   @Test
420   fun extendPlatformType() {
421     val result = compile(
422       kotlin(
423         "source.kt",
424         """
425           import com.squareup.moshi.JsonClass
426           import java.util.Date
427 
428           @JsonClass(generateAdapter = true)
429           class ExtendsPlatformClass(var a: Int) : Date()
430           """
431       )
432     )
433     assertThat(result.messages).contains("supertype java.util.Date is not a Kotlin type")
434   }
435 
436   @Test
437   fun extendJavaType() {
438     val result = compile(
439       kotlin(
440         "source.kt",
441         """
442           import com.squareup.moshi.JsonClass
443           import com.squareup.moshi.kotlin.codegen.JavaSuperclass
444 
445           @JsonClass(generateAdapter = true)
446           class ExtendsJavaType(var b: Int) : JavaSuperclass()
447           """
448       )
449     )
450     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
451     assertThat(result.messages)
452       .contains("supertype com.squareup.moshi.kotlin.codegen.JavaSuperclass is not a Kotlin type")
453   }
454 
455   @Test
456   fun nonFieldApplicableQualifier() {
457     val result = compile(
458       kotlin(
459         "source.kt",
460         """
461           import com.squareup.moshi.JsonClass
462           import com.squareup.moshi.JsonQualifier
463           import kotlin.annotation.AnnotationRetention.RUNTIME
464           import kotlin.annotation.AnnotationTarget.PROPERTY
465           import kotlin.annotation.Retention
466           import kotlin.annotation.Target
467 
468           @Retention(RUNTIME)
469           @Target(PROPERTY)
470           @JsonQualifier
471           annotation class UpperCase
472 
473           @JsonClass(generateAdapter = true)
474           class ClassWithQualifier(@UpperCase val a: Int)
475           """
476       )
477     )
478     // We instantiate directly so doesn't need to be FIELD
479     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
480   }
481 
482   @Test
483   fun nonRuntimeQualifier() {
484     val result = compile(
485       kotlin(
486         "source.kt",
487         """
488           import com.squareup.moshi.JsonClass
489           import com.squareup.moshi.JsonQualifier
490           import kotlin.annotation.AnnotationRetention.BINARY
491           import kotlin.annotation.AnnotationTarget.FIELD
492           import kotlin.annotation.AnnotationTarget.PROPERTY
493           import kotlin.annotation.Retention
494           import kotlin.annotation.Target
495 
496           @Retention(BINARY)
497           @Target(PROPERTY, FIELD)
498           @JsonQualifier
499           annotation class UpperCase
500 
501           @JsonClass(generateAdapter = true)
502           class ClassWithQualifier(@UpperCase val a: Int)
503           """
504       )
505     )
506     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
507     assertThat(result.messages).contains("JsonQualifier @UpperCase must have RUNTIME retention")
508   }
509 
510   @Test
511   fun `TypeAliases with the same backing type should share the same adapter`() {
512     val result = compile(
513       kotlin(
514         "source.kt",
515         """
516           import com.squareup.moshi.JsonClass
517 
518           typealias FirstName = String
519           typealias LastName = String
520 
521           @JsonClass(generateAdapter = true)
522           data class Person(val firstName: FirstName, val lastName: LastName, val hairColor: String)
523           """
524       )
525     )
526     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
527 
528     // We're checking here that we only generate one `stringAdapter` that's used for both the
529     // regular string properties as well as the the aliased ones.
530     val adapterClass = result.classLoader.loadClass("PersonJsonAdapter").kotlin
531     assertThat(adapterClass.declaredMemberProperties.map { it.returnType }).containsExactly(
532       JsonReader.Options::class.createType(),
533       JsonAdapter::class.parameterizedBy(String::class)
534     )
535   }
536 
537   @Test
538   fun `Processor should generate comprehensive proguard rules`() {
539     val result = compile(
540       kotlin(
541         "source.kt",
542         """
543           package testPackage
544           import com.squareup.moshi.JsonClass
545           import com.squareup.moshi.JsonQualifier
546 
547           typealias FirstName = String
548           typealias LastName = String
549 
550           @JsonClass(generateAdapter = true)
551           data class Aliases(val firstName: FirstName, val lastName: LastName, val hairColor: String)
552 
553           @JsonClass(generateAdapter = true)
554           data class Simple(val firstName: String)
555 
556           @JsonClass(generateAdapter = true)
557           data class Generic<T>(val firstName: T, val lastName: String)
558 
559           @JsonQualifier
560           annotation class MyQualifier
561 
562           @JsonClass(generateAdapter = true)
563           data class UsingQualifiers(val firstName: String, @MyQualifier val lastName: String)
564 
565           @JsonClass(generateAdapter = true)
566           data class MixedTypes(val firstName: String, val otherNames: MutableList<String>)
567 
568           @JsonClass(generateAdapter = true)
569           data class DefaultParams(val firstName: String = "")
570 
571           @JsonClass(generateAdapter = true)
572           data class Complex<T>(val firstName: FirstName = "", @MyQualifier val names: MutableList<String>, val genericProp: T)
573 
574           object NestedType {
575             @JsonQualifier
576             annotation class NestedQualifier
577 
578             @JsonClass(generateAdapter = true)
579             data class NestedSimple(@NestedQualifier val firstName: String)
580           }
581 
582           @JsonClass(generateAdapter = true)
583           class MultipleMasks(
584               val arg0: Long = 0,
585               val arg1: Long = 1,
586               val arg2: Long = 2,
587               val arg3: Long = 3,
588               val arg4: Long = 4,
589               val arg5: Long = 5,
590               val arg6: Long = 6,
591               val arg7: Long = 7,
592               val arg8: Long = 8,
593               val arg9: Long = 9,
594               val arg10: Long = 10,
595               val arg11: Long,
596               val arg12: Long = 12,
597               val arg13: Long = 13,
598               val arg14: Long = 14,
599               val arg15: Long = 15,
600               val arg16: Long = 16,
601               val arg17: Long = 17,
602               val arg18: Long = 18,
603               val arg19: Long = 19,
604               @Suppress("UNUSED_PARAMETER") arg20: Long = 20,
605               val arg21: Long = 21,
606               val arg22: Long = 22,
607               val arg23: Long = 23,
608               val arg24: Long = 24,
609               val arg25: Long = 25,
610               val arg26: Long = 26,
611               val arg27: Long = 27,
612               val arg28: Long = 28,
613               val arg29: Long = 29,
614               val arg30: Long = 30,
615               val arg31: Long = 31,
616               val arg32: Long = 32,
617               val arg33: Long = 33,
618               val arg34: Long = 34,
619               val arg35: Long = 35,
620               val arg36: Long = 36,
621               val arg37: Long = 37,
622               val arg38: Long = 38,
623               @Transient val arg39: Long = 39,
624               val arg40: Long = 40,
625               val arg41: Long = 41,
626               val arg42: Long = 42,
627               val arg43: Long = 43,
628               val arg44: Long = 44,
629               val arg45: Long = 45,
630               val arg46: Long = 46,
631               val arg47: Long = 47,
632               val arg48: Long = 48,
633               val arg49: Long = 49,
634               val arg50: Long = 50,
635               val arg51: Long = 51,
636               val arg52: Long = 52,
637               @Transient val arg53: Long = 53,
638               val arg54: Long = 54,
639               val arg55: Long = 55,
640               val arg56: Long = 56,
641               val arg57: Long = 57,
642               val arg58: Long = 58,
643               val arg59: Long = 59,
644               val arg60: Long = 60,
645               val arg61: Long = 61,
646               val arg62: Long = 62,
647               val arg63: Long = 63,
648               val arg64: Long = 64,
649               val arg65: Long = 65
650           )
651           """
652       )
653     )
654     assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
655 
656     result.generatedFiles.filter { it.extension == "pro" }.forEach { generatedFile ->
657       when (generatedFile.nameWithoutExtension) {
658         "moshi-testPackage.Aliases" -> assertThat(generatedFile.readText()).contains(
659           """
660           -if class testPackage.Aliases
661           -keepnames class testPackage.Aliases
662           -if class testPackage.Aliases
663           -keep class testPackage.AliasesJsonAdapter {
664               public <init>(com.squareup.moshi.Moshi);
665           }
666           """.trimIndent()
667         )
668         "moshi-testPackage.Simple" -> assertThat(generatedFile.readText()).contains(
669           """
670           -if class testPackage.Simple
671           -keepnames class testPackage.Simple
672           -if class testPackage.Simple
673           -keep class testPackage.SimpleJsonAdapter {
674               public <init>(com.squareup.moshi.Moshi);
675           }
676           """.trimIndent()
677         )
678         "moshi-testPackage.Generic" -> assertThat(generatedFile.readText()).contains(
679           """
680           -if class testPackage.Generic
681           -keepnames class testPackage.Generic
682           -if class testPackage.Generic
683           -keep class testPackage.GenericJsonAdapter {
684               public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
685           }
686           """.trimIndent()
687         )
688         "moshi-testPackage.UsingQualifiers" -> {
689           assertThat(generatedFile.readText()).contains(
690             """
691             -if class testPackage.UsingQualifiers
692             -keepnames class testPackage.UsingQualifiers
693             -if class testPackage.UsingQualifiers
694             -keep class testPackage.UsingQualifiersJsonAdapter {
695                 public <init>(com.squareup.moshi.Moshi);
696             }
697             """.trimIndent()
698           )
699         }
700         "moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
701           """
702           -if class testPackage.MixedTypes
703           -keepnames class testPackage.MixedTypes
704           -if class testPackage.MixedTypes
705           -keep class testPackage.MixedTypesJsonAdapter {
706               public <init>(com.squareup.moshi.Moshi);
707           }
708           """.trimIndent()
709         )
710         "moshi-testPackage.DefaultParams" -> assertThat(generatedFile.readText()).contains(
711           """
712           -if class testPackage.DefaultParams
713           -keepnames class testPackage.DefaultParams
714           -if class testPackage.DefaultParams
715           -keep class testPackage.DefaultParamsJsonAdapter {
716               public <init>(com.squareup.moshi.Moshi);
717           }
718           -if class testPackage.DefaultParams
719           -keepnames class kotlin.jvm.internal.DefaultConstructorMarker
720           -if class testPackage.DefaultParams
721           -keepclassmembers class testPackage.DefaultParams {
722               public synthetic <init>(java.lang.String,int,kotlin.jvm.internal.DefaultConstructorMarker);
723           }
724           """.trimIndent()
725         )
726         "moshi-testPackage.Complex" -> {
727           assertThat(generatedFile.readText()).contains(
728             """
729             -if class testPackage.Complex
730             -keepnames class testPackage.Complex
731             -if class testPackage.Complex
732             -keep class testPackage.ComplexJsonAdapter {
733                 public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
734             }
735             -if class testPackage.Complex
736             -keepnames class kotlin.jvm.internal.DefaultConstructorMarker
737             -if class testPackage.Complex
738             -keepclassmembers class testPackage.Complex {
739                 public synthetic <init>(java.lang.String,java.util.List,java.lang.Object,int,kotlin.jvm.internal.DefaultConstructorMarker);
740             }
741             """.trimIndent()
742           )
743         }
744         "moshi-testPackage.MultipleMasks" -> assertThat(generatedFile.readText()).contains(
745           """
746           -if class testPackage.MultipleMasks
747           -keepnames class testPackage.MultipleMasks
748           -if class testPackage.MultipleMasks
749           -keep class testPackage.MultipleMasksJsonAdapter {
750               public <init>(com.squareup.moshi.Moshi);
751           }
752           -if class testPackage.MultipleMasks
753           -keepnames class kotlin.jvm.internal.DefaultConstructorMarker
754           -if class testPackage.MultipleMasks
755           -keepclassmembers class testPackage.MultipleMasks {
756               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);
757           }
758           """.trimIndent()
759         )
760         "moshi-testPackage.NestedType.NestedSimple" -> {
761           assertThat(generatedFile.readText()).contains(
762             """
763             -if class testPackage.NestedType${'$'}NestedSimple
764             -keepnames class testPackage.NestedType${'$'}NestedSimple
765             -if class testPackage.NestedType${'$'}NestedSimple
766             -keep class testPackage.NestedType_NestedSimpleJsonAdapter {
767                 public <init>(com.squareup.moshi.Moshi);
768             }
769             """.trimIndent()
770           )
771         }
772         else -> error("Unexpected proguard file! ${generatedFile.name}")
773       }
774     }
775   }
776 
777   private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation {
778     return KotlinCompilation()
779       .apply {
780         workingDir = temporaryFolder.root
781         annotationProcessors = listOf(JsonClassCodegenProcessor())
782         inheritClassPath = true
783         sources = sourceFiles.asList()
784         verbose = false
785       }
786   }
787 
788   private fun compile(vararg sourceFiles: SourceFile): KotlinCompilation.Result {
789     return prepareCompilation(*sourceFiles).compile()
790   }
791 
792   private fun KClassifier.parameterizedBy(vararg types: KClass<*>): KType {
793     return parameterizedBy(*types.map { it.createType() }.toTypedArray())
794   }
795 
796   private fun KClassifier.parameterizedBy(vararg types: KType): KType {
797     return createType(
798       types.map { it.asProjection() }
799     )
800   }
801 
802   private fun KType.asProjection(variance: KVariance? = INVARIANT): KTypeProjection {
803     return KTypeProjection(variance, this)
804   }
805 }
806