mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 07:29:22 +08:00
Remove KAPT support (#1848)
* Remove KAPT support * Stick KSP on the root classpath * Another * Another
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
kotlin-test-mode: [ 'REFLECT', 'KSP', 'KAPT' ]
|
||||
kotlin-test-mode: [ 'REFLECT', 'KSP' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
20
README.md
20
README.md
@@ -1076,7 +1076,7 @@ Note that the reflection adapter transitively depends on the `kotlin-reflect` li
|
||||
|
||||
#### Codegen
|
||||
|
||||
Moshi’s Kotlin codegen support can be used as an annotation processor (via [kapt][kapt]) or Kotlin SymbolProcessor ([KSP][ksp]).
|
||||
Moshi’s Kotlin codegen support can be used as a Kotlin SymbolProcessor ([KSP][ksp]).
|
||||
It generates a small and fast adapter for each of your Kotlin classes at compile-time. Enable it by annotating
|
||||
each class that you want to encode as JSON:
|
||||
|
||||
@@ -1109,23 +1109,6 @@ dependencies {
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Kapt</summary>
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.squareup.moshi</groupId>
|
||||
<artifactId>moshi-kotlin-codegen</artifactId>
|
||||
<version>1.15.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
```kotlin
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
|
||||
```
|
||||
</details>
|
||||
|
||||
#### Limitations
|
||||
|
||||
If your Kotlin class has a superclass, it must also be a Kotlin class. Neither reflection or codegen
|
||||
@@ -1190,5 +1173,4 @@ License
|
||||
[okhttp]: https://github.com/square/okhttp/
|
||||
[gson]: https://github.com/google/gson/
|
||||
[javadoc]: https://square.github.io/moshi/1.x/moshi/
|
||||
[kapt]: https://kotlinlang.org/docs/reference/kapt.html
|
||||
[ksp]: https://github.com/google/ksp
|
||||
|
@@ -27,6 +27,7 @@ plugins {
|
||||
alias(libs.plugins.dokka) apply false
|
||||
alias(libs.plugins.spotless)
|
||||
alias(libs.plugins.japicmp) apply false
|
||||
alias(libs.plugins.ksp) apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
@@ -2,11 +2,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("kapt")
|
||||
alias(libs.plugins.ksp)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt(project(":moshi-kotlin-codegen"))
|
||||
ksp(project(":moshi-kotlin-codegen"))
|
||||
compileOnly(libs.jsr305)
|
||||
implementation(project(":moshi"))
|
||||
implementation(project(":moshi-adapters"))
|
||||
|
@@ -1,4 +1,2 @@
|
||||
# Memory for Dokka https://github.com/Kotlin/dokka/issues/1405
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
|
||||
kapt.include.compile.classpath=false
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer
|
||||
import com.vanniktech.maven.publish.JavadocJar.None
|
||||
import com.vanniktech.maven.publish.KotlinJvm
|
||||
import com.vanniktech.maven.publish.MavenPublishBaseExtension
|
||||
@@ -8,7 +7,6 @@ plugins {
|
||||
kotlin("jvm")
|
||||
id("com.google.devtools.ksp")
|
||||
id("com.vanniktech.maven.publish.base")
|
||||
alias(libs.plugins.mavenShadow)
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
@@ -26,35 +24,9 @@ tasks.compileTestKotlin {
|
||||
}
|
||||
}
|
||||
|
||||
// --add-opens for kapt to work. KGP covers this for us but local JVMs in tests do not
|
||||
tasks.withType<Test>().configureEach {
|
||||
jvmArgs(
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||
"--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||
)
|
||||
}
|
||||
|
||||
val shade: Configuration = configurations.maybeCreate("compileShaded")
|
||||
configurations.getByName("compileOnly").extendsFrom(shade)
|
||||
dependencies {
|
||||
implementation(project(":moshi"))
|
||||
shade(libs.kotlinxMetadata) {
|
||||
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
|
||||
}
|
||||
api(libs.kotlinpoet)
|
||||
shade(libs.kotlinpoet.metadata) {
|
||||
exclude(group = "org.jetbrains.kotlin")
|
||||
exclude(group = "com.squareup", module = "kotlinpoet")
|
||||
exclude(group = "com.google.guava")
|
||||
}
|
||||
implementation(libs.kotlinpoet.ksp)
|
||||
implementation(libs.guava)
|
||||
implementation(libs.asm)
|
||||
@@ -83,26 +55,6 @@ dependencies {
|
||||
testImplementation(libs.kotlinCompileTesting)
|
||||
}
|
||||
|
||||
val shadowJar =
|
||||
tasks.shadowJar.apply {
|
||||
configure {
|
||||
archiveClassifier.set("")
|
||||
configurations = listOf(shade)
|
||||
relocate("com.squareup.kotlinpoet.metadata", "com.squareup.moshi.kotlinpoet.metadata")
|
||||
relocate(
|
||||
"com.squareup.kotlinpoet.classinspector",
|
||||
"com.squareup.moshi.kotlinpoet.classinspector",
|
||||
)
|
||||
relocate("kotlinx.metadata", "com.squareup.moshi.kotlinx.metadata")
|
||||
transformers.add(ServiceFileTransformer())
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
runtimeOnly(shadowJar)
|
||||
archives(shadowJar)
|
||||
}
|
||||
|
||||
configure<MavenPublishBaseExtension> {
|
||||
configure(KotlinJvm(javadocJar = None()))
|
||||
}
|
||||
|
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.apt
|
||||
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
|
||||
import com.squareup.kotlinpoet.asClassName
|
||||
import javax.lang.model.element.ElementKind.CLASS
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.type.DeclaredType
|
||||
import javax.lang.model.util.Types
|
||||
|
||||
private val OBJECT_CLASS = ClassName("java.lang", "Object")
|
||||
|
||||
/**
|
||||
* A concrete type like `List<String>` with enough information to know how to resolve its type
|
||||
* variables.
|
||||
*/
|
||||
internal class AppliedType private constructor(
|
||||
val element: TypeElement,
|
||||
private val mirror: DeclaredType,
|
||||
) {
|
||||
/** Returns all supertypes of this, recursively. Only [CLASS] is used as we can't really use other types. */
|
||||
@OptIn(DelicateKotlinPoetApi::class)
|
||||
fun superclasses(
|
||||
types: Types,
|
||||
result: LinkedHashSet<AppliedType> = LinkedHashSet(),
|
||||
): LinkedHashSet<AppliedType> {
|
||||
result.add(this)
|
||||
for (supertype in types.directSupertypes(mirror)) {
|
||||
val supertypeDeclaredType = supertype as DeclaredType
|
||||
val supertypeElement = supertypeDeclaredType.asElement() as TypeElement
|
||||
if (supertypeElement.kind != CLASS) {
|
||||
continue
|
||||
} else if (supertypeElement.asClassName() == OBJECT_CLASS) {
|
||||
// Don't load properties for java.lang.Object.
|
||||
continue
|
||||
}
|
||||
val appliedSuperclass = AppliedType(supertypeElement, supertypeDeclaredType)
|
||||
appliedSuperclass.superclasses(types, result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString() = mirror.toString()
|
||||
|
||||
companion object {
|
||||
operator fun invoke(typeElement: TypeElement): AppliedType {
|
||||
return AppliedType(typeElement, typeElement.asType() as DeclaredType)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.apt
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.metadata.classinspectors.ElementsClassInspector
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator
|
||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATED
|
||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATE_PROGUARD_RULES
|
||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_INSTANTIATE_ANNOTATIONS
|
||||
import com.squareup.moshi.kotlin.codegen.api.Options.POSSIBLE_GENERATED_NAMES
|
||||
import com.squareup.moshi.kotlin.codegen.api.ProguardConfig
|
||||
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
||||
import javax.annotation.processing.AbstractProcessor
|
||||
import javax.annotation.processing.Filer
|
||||
import javax.annotation.processing.Messager
|
||||
import javax.annotation.processing.ProcessingEnvironment
|
||||
import javax.annotation.processing.Processor
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import javax.lang.model.SourceVersion
|
||||
import javax.lang.model.element.Element
|
||||
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.StandardLocation
|
||||
|
||||
/**
|
||||
* An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them.
|
||||
* This generates Kotlin code, and understands basic Kotlin language features like default values
|
||||
* and companion objects.
|
||||
*
|
||||
* The generated class will match the visibility of the given data class (i.e. if it's internal, the
|
||||
* adapter will also be internal).
|
||||
*/
|
||||
@AutoService(Processor::class)
|
||||
public class JsonClassCodegenProcessor : AbstractProcessor() {
|
||||
|
||||
private lateinit var types: Types
|
||||
private lateinit var elements: Elements
|
||||
private lateinit var filer: Filer
|
||||
private lateinit var messager: Messager
|
||||
private lateinit var cachedClassInspector: MoshiCachedClassInspector
|
||||
private val annotation = JsonClass::class.java
|
||||
private var generatedType: ClassName? = null
|
||||
private var generateProguardRules: Boolean = true
|
||||
private var instantiateAnnotations: Boolean = true
|
||||
|
||||
override fun getSupportedAnnotationTypes(): Set<String> = setOf(annotation.canonicalName)
|
||||
|
||||
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
|
||||
|
||||
override fun getSupportedOptions(): Set<String> = setOf(OPTION_GENERATED)
|
||||
|
||||
override fun init(processingEnv: ProcessingEnvironment) {
|
||||
super.init(processingEnv)
|
||||
generatedType = processingEnv.options[OPTION_GENERATED]?.let {
|
||||
POSSIBLE_GENERATED_NAMES[it] ?: error(
|
||||
"Invalid option value for $OPTION_GENERATED. Found $it, " +
|
||||
"allowable values are $POSSIBLE_GENERATED_NAMES.",
|
||||
)
|
||||
}
|
||||
|
||||
generateProguardRules = processingEnv.options[OPTION_GENERATE_PROGUARD_RULES]?.toBooleanStrictOrNull() ?: true
|
||||
instantiateAnnotations = processingEnv.options[OPTION_INSTANTIATE_ANNOTATIONS]?.toBooleanStrictOrNull() ?: true
|
||||
|
||||
this.types = processingEnv.typeUtils
|
||||
this.elements = processingEnv.elementUtils
|
||||
this.filer = processingEnv.filer
|
||||
this.messager = processingEnv.messager
|
||||
cachedClassInspector = MoshiCachedClassInspector(ElementsClassInspector.create(elements, types))
|
||||
}
|
||||
|
||||
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||
if (roundEnv.errorRaised()) {
|
||||
// An error was raised in the previous round. Don't try anything for now to avoid adding
|
||||
// possible more noise.
|
||||
return false
|
||||
}
|
||||
for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
|
||||
if (type !is TypeElement) {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"@JsonClass can't be applied to $type: must be a Kotlin class",
|
||||
type,
|
||||
)
|
||||
continue
|
||||
}
|
||||
val jsonClass = type.getAnnotation(annotation)
|
||||
if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
|
||||
val generator = adapterGenerator(type, cachedClassInspector) ?: continue
|
||||
val preparedAdapter = generator
|
||||
.prepare(generateProguardRules) { spec ->
|
||||
spec.toBuilder()
|
||||
.apply {
|
||||
@Suppress("DEPRECATION") // This is a Java type
|
||||
generatedType?.let { generatedClassName ->
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(generatedClassName)
|
||||
.addMember(
|
||||
"value = [%S]",
|
||||
JsonClassCodegenProcessor::class.java.canonicalName,
|
||||
)
|
||||
.addMember("comments = %S", "https://github.com/square/moshi")
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
.addOriginatingElement(type)
|
||||
.build()
|
||||
}
|
||||
|
||||
preparedAdapter.spec.writeTo(filer)
|
||||
preparedAdapter.proguardConfig?.writeTo(filer, type)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun adapterGenerator(
|
||||
element: TypeElement,
|
||||
cachedClassInspector: MoshiCachedClassInspector,
|
||||
): AdapterGenerator? {
|
||||
val type = targetType(
|
||||
messager,
|
||||
elements,
|
||||
types,
|
||||
element,
|
||||
cachedClassInspector,
|
||||
) ?: return null
|
||||
|
||||
val properties = mutableMapOf<String, PropertyGenerator>()
|
||||
for (property in type.properties.values) {
|
||||
val generator = property.generator(messager, element, elements)
|
||||
if (generator != null) {
|
||||
properties[property.name] = generator
|
||||
}
|
||||
}
|
||||
|
||||
for ((name, parameter) in type.constructor.parameters) {
|
||||
if (type.properties[parameter.name] == null && !parameter.hasDefault) {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"No property for required constructor parameter $name",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Sort properties so that those with constructor parameters come first.
|
||||
val sortedProperties = properties.values.sortedBy {
|
||||
if (it.hasConstructorParameter) {
|
||||
it.target.parameterIndex
|
||||
} else {
|
||||
Integer.MAX_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
return AdapterGenerator(type, sortedProperties)
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes this config to a [filer]. */
|
||||
private fun ProguardConfig.writeTo(filer: Filer, vararg originatingElements: Element) {
|
||||
filer.createResource(StandardLocation.CLASS_OUTPUT, "", "${outputFilePathWithoutExtension(targetClass.canonicalName)}.pro", *originatingElements)
|
||||
.openWriter()
|
||||
.use(::writeTo)
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.apt
|
||||
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.metadata.specs.ClassInspector
|
||||
import com.squareup.kotlinpoet.metadata.specs.toTypeSpec
|
||||
import com.squareup.kotlinpoet.metadata.toKmClass
|
||||
import kotlinx.metadata.KmClass
|
||||
import java.util.TreeMap
|
||||
import javax.lang.model.element.TypeElement
|
||||
|
||||
/** KmClass doesn't implement equality natively. */
|
||||
private val KmClassComparator = compareBy<KmClass> { it.name }
|
||||
|
||||
/**
|
||||
* This cached API over [ClassInspector] that caches certain lookups Moshi does potentially multiple
|
||||
* times. This is useful mostly because it avoids duplicate reloads in cases like common base
|
||||
* classes, common enclosing types, etc.
|
||||
*/
|
||||
internal class MoshiCachedClassInspector(private val classInspector: ClassInspector) {
|
||||
private val elementToSpecCache = mutableMapOf<TypeElement, TypeSpec>()
|
||||
private val kmClassToSpecCache = TreeMap<KmClass, TypeSpec>(KmClassComparator)
|
||||
private val metadataToKmClassCache = mutableMapOf<Metadata, KmClass>()
|
||||
|
||||
fun toKmClass(metadata: Metadata): KmClass {
|
||||
return metadataToKmClassCache.getOrPut(metadata) {
|
||||
metadata.toKmClass()
|
||||
}
|
||||
}
|
||||
|
||||
fun toTypeSpec(kmClass: KmClass): TypeSpec {
|
||||
return kmClassToSpecCache.getOrPut(kmClass) {
|
||||
kmClass.toTypeSpec(classInspector)
|
||||
}
|
||||
}
|
||||
|
||||
fun toTypeSpec(element: TypeElement): TypeSpec {
|
||||
return elementToSpecCache.getOrPut(element) {
|
||||
toTypeSpec(toKmClass(element.metadata))
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,539 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.apt
|
||||
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
import com.squareup.kotlinpoet.asClassName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
|
||||
import com.squareup.kotlinpoet.metadata.isAbstract
|
||||
import com.squareup.kotlinpoet.metadata.isClass
|
||||
import com.squareup.kotlinpoet.metadata.isEnum
|
||||
import com.squareup.kotlinpoet.metadata.isInner
|
||||
import com.squareup.kotlinpoet.metadata.isInternal
|
||||
import com.squareup.kotlinpoet.metadata.isLocal
|
||||
import com.squareup.kotlinpoet.metadata.isPublic
|
||||
import com.squareup.kotlinpoet.metadata.isSealed
|
||||
import com.squareup.kotlinpoet.tag
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import com.squareup.moshi.kotlin.codegen.api.DelegateKey
|
||||
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
||||
import com.squareup.moshi.kotlin.codegen.api.TargetConstructor
|
||||
import com.squareup.moshi.kotlin.codegen.api.TargetParameter
|
||||
import com.squareup.moshi.kotlin.codegen.api.TargetProperty
|
||||
import com.squareup.moshi.kotlin.codegen.api.TargetType
|
||||
import com.squareup.moshi.kotlin.codegen.api.rawType
|
||||
import com.squareup.moshi.kotlin.codegen.api.unwrapTypeAlias
|
||||
import kotlinx.metadata.KmConstructor
|
||||
import kotlinx.metadata.jvm.signature
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
import javax.annotation.processing.Messager
|
||||
import javax.lang.model.element.AnnotationMirror
|
||||
import javax.lang.model.element.Element
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.type.DeclaredType
|
||||
import javax.lang.model.util.Elements
|
||||
import javax.lang.model.util.Types
|
||||
import javax.tools.Diagnostic.Kind.ERROR
|
||||
import javax.tools.Diagnostic.Kind.WARNING
|
||||
|
||||
private val JSON_QUALIFIER = JsonQualifier::class.java
|
||||
private val JSON = Json::class.asClassName()
|
||||
private val TRANSIENT = Transient::class.asClassName()
|
||||
private val VISIBILITY_MODIFIERS = setOf(
|
||||
KModifier.INTERNAL,
|
||||
KModifier.PRIVATE,
|
||||
KModifier.PROTECTED,
|
||||
KModifier.PUBLIC,
|
||||
)
|
||||
|
||||
private fun Collection<KModifier>.visibility(): KModifier {
|
||||
return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC
|
||||
}
|
||||
|
||||
@KotlinPoetMetadataPreview
|
||||
internal fun primaryConstructor(
|
||||
targetElement: TypeElement,
|
||||
kotlinApi: TypeSpec,
|
||||
elements: Elements,
|
||||
messager: Messager,
|
||||
): TargetConstructor? {
|
||||
val primaryConstructor = kotlinApi.primaryConstructor ?: return null
|
||||
|
||||
val parameters = LinkedHashMap<String, TargetParameter>()
|
||||
for ((index, parameter) in primaryConstructor.parameters.withIndex()) {
|
||||
val name = parameter.name
|
||||
parameters[name] = TargetParameter(
|
||||
name = name,
|
||||
index = index,
|
||||
type = parameter.type,
|
||||
hasDefault = parameter.defaultValue != null,
|
||||
qualifiers = parameter.annotations.qualifiers(messager, elements),
|
||||
jsonName = parameter.annotations.jsonName(),
|
||||
jsonIgnore = parameter.annotations.jsonIgnore(),
|
||||
)
|
||||
}
|
||||
|
||||
val kmConstructorSignature = primaryConstructor.tag<KmConstructor>()?.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. */
|
||||
@OptIn(DelicateKotlinPoetApi::class)
|
||||
@KotlinPoetMetadataPreview
|
||||
internal fun targetType(
|
||||
messager: Messager,
|
||||
elements: Elements,
|
||||
types: Types,
|
||||
element: TypeElement,
|
||||
cachedClassInspector: MoshiCachedClassInspector,
|
||||
): TargetType? {
|
||||
val typeMetadata = element.getAnnotation(Metadata::class.java)
|
||||
if (typeMetadata == null) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
val kmClass = try {
|
||||
cachedClassInspector.toKmClass(typeMetadata)
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must be a Class type",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
when {
|
||||
kmClass.isEnum -> {
|
||||
messager.printMessage(
|
||||
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(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
kmClass.isInner -> {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must not be an inner class",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
kmClass.flags.isSealed -> {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must not be sealed",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
kmClass.flags.isAbstract -> {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must not be abstract",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
kmClass.flags.isLocal -> {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must not be local",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
!kmClass.flags.isPublic && !kmClass.flags.isInternal -> {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: must be internal or public",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val kotlinApi = cachedClassInspector.toTypeSpec(kmClass)
|
||||
val typeVariables = kotlinApi.typeVariables
|
||||
val appliedType = AppliedType(element)
|
||||
|
||||
val constructor = primaryConstructor(element, kotlinApi, elements, messager)
|
||||
if (constructor == null) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"No primary constructor found on $element",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: " +
|
||||
"primary constructor is not internal or public",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
val properties = mutableMapOf<String, TargetProperty>()
|
||||
|
||||
val resolvedTypes = mutableListOf<ResolvedTypeMapping>()
|
||||
val superclass = appliedType.superclasses(types)
|
||||
.onEach { superclass ->
|
||||
if (superclass.element.getAnnotation(Metadata::class.java) == null) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"@JsonClass can't be applied to $element: supertype $superclass is not a Kotlin type",
|
||||
element,
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
.associateWithTo(LinkedHashMap()) { superclass ->
|
||||
// Load the kotlin API cache into memory eagerly so we can reuse the parsed APIs
|
||||
val api = if (superclass.element == element) {
|
||||
// We've already parsed this api above, reuse it
|
||||
kotlinApi
|
||||
} else {
|
||||
cachedClassInspector.toTypeSpec(superclass.element)
|
||||
}
|
||||
|
||||
val apiSuperClass = api.superclass
|
||||
if (apiSuperClass is ParameterizedTypeName) {
|
||||
//
|
||||
// This extends a typed generic superclass. We want to construct a mapping of the
|
||||
// superclass typevar names to their materialized types here.
|
||||
//
|
||||
// class Foo extends Bar<String>
|
||||
// class Bar<T>
|
||||
//
|
||||
// We will store {Foo : {T : [String]}}.
|
||||
//
|
||||
// Then when we look at Bar<T> later, we'll look up to the descendent Foo and extract its
|
||||
// materialized type from there.
|
||||
//
|
||||
val superSuperClass = superclass.element.superclass as DeclaredType
|
||||
|
||||
// Convert to an element and back to wipe the typed generics off of this
|
||||
val untyped = superSuperClass.asElement().asType().asTypeName() as ParameterizedTypeName
|
||||
resolvedTypes += ResolvedTypeMapping(
|
||||
target = untyped.rawType,
|
||||
args = untyped.typeArguments.asSequence()
|
||||
.cast<TypeVariableName>()
|
||||
.map(TypeVariableName::name)
|
||||
.zip(apiSuperClass.typeArguments.asSequence())
|
||||
.associate { it },
|
||||
)
|
||||
}
|
||||
|
||||
return@associateWithTo api
|
||||
}
|
||||
|
||||
for ((localAppliedType, supertypeApi) in superclass.entries) {
|
||||
val appliedClassName = localAppliedType.element.asClassName()
|
||||
val supertypeProperties = declaredProperties(
|
||||
constructor = constructor,
|
||||
kotlinApi = supertypeApi,
|
||||
allowedTypeVars = typeVariables.toSet(),
|
||||
currentClass = appliedClassName,
|
||||
resolvedTypes = resolvedTypes,
|
||||
)
|
||||
for ((name, property) in supertypeProperties) {
|
||||
properties.putIfAbsent(name, property)
|
||||
}
|
||||
}
|
||||
val visibility = kotlinApi.modifiers.visibility()
|
||||
// If any class in the enclosing class hierarchy is internal, they must all have internal
|
||||
// generated adapters.
|
||||
val resolvedVisibility = if (visibility == KModifier.INTERNAL) {
|
||||
// Our nested type is already internal, no need to search
|
||||
visibility
|
||||
} else {
|
||||
// Implicitly public, so now look up the hierarchy
|
||||
val forceInternal = generateSequence<Element>(element) { it.enclosingElement }
|
||||
.filterIsInstance<TypeElement>()
|
||||
.map { cachedClassInspector.toKmClass(it.metadata) }
|
||||
.any { it.flags.isInternal }
|
||||
if (forceInternal) KModifier.INTERNAL else visibility
|
||||
}
|
||||
|
||||
return TargetType(
|
||||
typeName = element.asType().asTypeName(),
|
||||
constructor = constructor,
|
||||
properties = properties,
|
||||
typeVariables = typeVariables,
|
||||
isDataClass = KModifier.DATA in kotlinApi.modifiers,
|
||||
visibility = resolvedVisibility,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a resolved raw class to type arguments where [args] are a map of the parent type var
|
||||
* name to its resolved [TypeName].
|
||||
*/
|
||||
private data class ResolvedTypeMapping(val target: ClassName, val args: Map<String, TypeName>)
|
||||
|
||||
private fun resolveTypeArgs(
|
||||
targetClass: ClassName,
|
||||
propertyType: TypeName,
|
||||
resolvedTypes: List<ResolvedTypeMapping>,
|
||||
allowedTypeVars: Set<TypeVariableName>,
|
||||
entryStartIndex: Int = resolvedTypes.indexOfLast { it.target == targetClass },
|
||||
): TypeName {
|
||||
val unwrappedType = propertyType.unwrapTypeAlias()
|
||||
|
||||
if (unwrappedType !is TypeVariableName) {
|
||||
return unwrappedType
|
||||
} else if (entryStartIndex == -1) {
|
||||
return unwrappedType
|
||||
}
|
||||
|
||||
val targetMappingIndex = resolvedTypes[entryStartIndex]
|
||||
val targetMappings = targetMappingIndex.args
|
||||
|
||||
// Try to resolve the real type of this property based on mapped generics in the subclass.
|
||||
// We need to us a non-nullable version for mapping since we're just mapping based on raw java
|
||||
// type vars, but then can re-copy nullability back if it is found.
|
||||
val resolvedType = targetMappings[unwrappedType.name]
|
||||
?.copy(nullable = unwrappedType.isNullable)
|
||||
?: unwrappedType
|
||||
|
||||
return when {
|
||||
resolvedType !is TypeVariableName -> resolvedType
|
||||
|
||||
entryStartIndex != 0 -> {
|
||||
// We need to go deeper
|
||||
resolveTypeArgs(targetClass, resolvedType, resolvedTypes, allowedTypeVars, entryStartIndex - 1)
|
||||
}
|
||||
|
||||
resolvedType.copy(nullable = false) in allowedTypeVars -> {
|
||||
// This is a generic type in the top-level declared class. This is fine to leave in because
|
||||
// this will be handled by the `Type` array passed in at runtime.
|
||||
resolvedType
|
||||
}
|
||||
|
||||
else -> error("Could not find $resolvedType in $resolvedTypes. Also not present in allowable top-level type vars $allowedTypeVars")
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the properties declared by `typeElement`. */
|
||||
@KotlinPoetMetadataPreview
|
||||
private fun declaredProperties(
|
||||
constructor: TargetConstructor,
|
||||
kotlinApi: TypeSpec,
|
||||
allowedTypeVars: Set<TypeVariableName>,
|
||||
currentClass: ClassName,
|
||||
resolvedTypes: List<ResolvedTypeMapping>,
|
||||
): Map<String, TargetProperty> {
|
||||
val result = mutableMapOf<String, TargetProperty>()
|
||||
for (initialProperty in kotlinApi.propertySpecs) {
|
||||
val resolvedType = resolveTypeArgs(
|
||||
targetClass = currentClass,
|
||||
propertyType = initialProperty.type,
|
||||
resolvedTypes = resolvedTypes,
|
||||
allowedTypeVars = allowedTypeVars,
|
||||
)
|
||||
val property = initialProperty.toBuilder(type = resolvedType).build()
|
||||
val name = property.name
|
||||
val parameter = constructor.parameters[name]
|
||||
val isIgnored = property.annotations.any { it.typeName == TRANSIENT } ||
|
||||
parameter?.jsonIgnore == true ||
|
||||
property.annotations.jsonIgnore()
|
||||
result[name] = TargetProperty(
|
||||
propertySpec = property,
|
||||
parameter = parameter,
|
||||
visibility = property.modifiers.visibility(),
|
||||
jsonName = parameter?.jsonName ?: property.annotations.jsonName() ?: name,
|
||||
jsonIgnore = isIgnored,
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
|
||||
private val TargetProperty.isVisible: Boolean
|
||||
get() {
|
||||
return visibility == KModifier.INTERNAL ||
|
||||
visibility == KModifier.PROTECTED ||
|
||||
visibility == KModifier.PUBLIC
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generator for this property, or null if either there is an error and this property
|
||||
* cannot be used with code gen, or if no codegen is necessary for this property.
|
||||
*/
|
||||
internal fun TargetProperty.generator(
|
||||
messager: Messager,
|
||||
sourceElement: TypeElement,
|
||||
elements: Elements,
|
||||
): PropertyGenerator? {
|
||||
if (jsonIgnore) {
|
||||
if (!hasDefault) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"No default value for transient/ignored property $name",
|
||||
sourceElement,
|
||||
)
|
||||
return null
|
||||
}
|
||||
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
|
||||
}
|
||||
|
||||
if (!isVisible) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"property $name is not visible",
|
||||
sourceElement,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isSettable) {
|
||||
return null // This property is not settable. Ignore it.
|
||||
}
|
||||
|
||||
// Merge parameter and property annotations
|
||||
val qualifiers = parameter?.qualifiers.orEmpty() + propertySpec.annotations.qualifiers(messager, elements)
|
||||
for (jsonQualifier in qualifiers) {
|
||||
val qualifierRawType = jsonQualifier.typeName.rawType()
|
||||
// Check Java types since that covers both Java and Kotlin annotations.
|
||||
val annotationElement = elements.getTypeElement(qualifierRawType.canonicalName)
|
||||
?: continue
|
||||
|
||||
annotationElement.getAnnotation(Retention::class.java)?.let {
|
||||
if (it.value != RetentionPolicy.RUNTIME) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"JsonQualifier @${qualifierRawType.simpleName} must have RUNTIME retention",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val jsonQualifierSpecs = qualifiers.map {
|
||||
it.toBuilder()
|
||||
.useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
|
||||
.build()
|
||||
}
|
||||
|
||||
return PropertyGenerator(
|
||||
this,
|
||||
DelegateKey(type, jsonQualifierSpecs),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<AnnotationSpec>?.qualifiers(
|
||||
messager: Messager,
|
||||
elements: Elements,
|
||||
): Set<AnnotationSpec> {
|
||||
if (this == null) return setOf()
|
||||
return filterTo(mutableSetOf()) {
|
||||
val typeElement: TypeElement? = elements.getTypeElement(it.typeName.rawType().canonicalName)
|
||||
if (typeElement == null) {
|
||||
messager.printMessage(WARNING, "Could not get the TypeElement of $it")
|
||||
}
|
||||
typeElement?.getAnnotation(JSON_QUALIFIER) != null
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<AnnotationSpec>?.jsonName(): String? {
|
||||
if (this == null) return null
|
||||
return filter { it.typeName == JSON }.firstNotNullOfOrNull { annotation ->
|
||||
annotation.jsonName()
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<AnnotationSpec>?.jsonIgnore(): Boolean {
|
||||
if (this == null) return false
|
||||
return filter { it.typeName == JSON }.firstNotNullOfOrNull { annotation ->
|
||||
annotation.jsonIgnore()
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private fun AnnotationSpec.jsonName(): String? {
|
||||
return elementValue<String>("name").takeUnless { it == Json.UNSET_NAME }
|
||||
}
|
||||
|
||||
private fun AnnotationSpec.jsonIgnore(): Boolean {
|
||||
return elementValue<Boolean>("ignore") ?: false
|
||||
}
|
||||
|
||||
private fun <T> AnnotationSpec.elementValue(name: String): T? {
|
||||
val mirror = requireNotNull(tag<AnnotationMirror>()) {
|
||||
"Could not get the annotation mirror from the annotation spec"
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return mirror.elementValues.entries.firstOrNull {
|
||||
it.key.simpleName.contentEquals(name)
|
||||
}?.value?.value as? T
|
||||
}
|
||||
|
||||
internal val TypeElement.metadata: Metadata
|
||||
get() {
|
||||
return getAnnotation(Metadata::class.java)
|
||||
?: throw IllegalStateException("Not a kotlin type! $this")
|
||||
}
|
||||
|
||||
private fun <E> Sequence<*>.cast(): Sequence<E> {
|
||||
return map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
it as E
|
||||
}
|
||||
}
|
@@ -1 +0,0 @@
|
||||
com.squareup.moshi.kotlin.codegen.apt.JsonClassCodegenProcessor,ISOLATING
|
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen;
|
||||
|
||||
import com.squareup.moshi.kotlin.codegen.apt.JsonClassCodegenProcessorTest;
|
||||
import com.squareup.moshi.kotlin.codegen.ksp.JsonClassSymbolProcessorTest;
|
||||
|
||||
/** For {@link JsonClassCodegenProcessorTest#extendJavaType}. */
|
||||
/** For {@link JsonClassSymbolProcessorTest#extendJavaType}. */
|
||||
public class JavaSuperclass {
|
||||
public int a = 1;
|
||||
}
|
||||
|
@@ -1,817 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.apt
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATED
|
||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATE_PROGUARD_RULES
|
||||
import com.tschuchort.compiletesting.JvmCompilationResult
|
||||
import com.tschuchort.compiletesting.KotlinCompilation
|
||||
import com.tschuchort.compiletesting.SourceFile
|
||||
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KClassifier
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.KTypeProjection
|
||||
import kotlin.reflect.KVariance
|
||||
import kotlin.reflect.KVariance.INVARIANT
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
|
||||
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
|
||||
class JsonClassCodegenProcessorTest {
|
||||
|
||||
@Rule @JvmField
|
||||
val temporaryFolder: TemporaryFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun privateConstructor() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PrivateConstructor private constructor(val a: Int, val b: Int) {
|
||||
fun a() = a
|
||||
fun b() = b
|
||||
companion object {
|
||||
fun newInstance(a: Int, b: Int) = PrivateConstructor(a, b)
|
||||
}
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("constructor is not internal or public")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun privateConstructorParameter() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PrivateConstructorParameter(private var a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("property a is not visible")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun privateProperties() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PrivateProperties {
|
||||
private var a: Int = -1
|
||||
private var b: Int = -1
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("property a is not visible")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun interfacesNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
interface Interface
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to Interface: must be a Kotlin class",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun interfacesDoNotErrorWhenGeneratorNotSet() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true, generator="customGenerator")
|
||||
interface Interface
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun abstractClassesNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
abstract class AbstractClass(val a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to AbstractClass: must not be abstract",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sealedClassesNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
sealed class SealedClass(val a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to SealedClass: must not be sealed",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun innerClassesNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
class Outer {
|
||||
@JsonClass(generateAdapter = true)
|
||||
inner class InnerClass(val a: Int)
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to Outer.InnerClass: must not be an inner class",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enumClassesNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
enum class KotlinEnum {
|
||||
A, B
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass with 'generateAdapter = \"true\"' can't be applied to KotlinEnum: code gen for enums is not supported or necessary",
|
||||
)
|
||||
}
|
||||
|
||||
// Annotation processors don't get called for local classes, so we don't have the opportunity to
|
||||
@Ignore
|
||||
@Test
|
||||
fun localClassesNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
fun outer() {
|
||||
@JsonClass(generateAdapter = true)
|
||||
class LocalClass(val a: Int)
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to LocalClass: must not be local",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun privateClassesNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
private class PrivateClass(val a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to PrivateClass: must be internal or public",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun objectDeclarationsNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
object ObjectDeclaration {
|
||||
var a = 5
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to ObjectDeclaration: must be a Kotlin class",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun objectExpressionsNotSupported() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
val expression = object : Any() {
|
||||
var a = 5
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to getExpression\$annotations(): must be a Kotlin class",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requiredTransientConstructorParameterFails() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class RequiredTransientConstructorParameter(@Transient var a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: No default value for transient/ignored property a",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requiredIgnoredConstructorParameterFails() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class RequiredIgnoredConstructorParameter(@Json(ignore = true) var a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: No default value for transient/ignored property a",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonPropertyConstructorParameter() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class NonPropertyConstructorParameter(a: Int, val b: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: No property for required constructor parameter a",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun badGeneratedAnnotation() {
|
||||
val result = prepareCompilation(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Foo(val a: Int)
|
||||
""",
|
||||
),
|
||||
).apply {
|
||||
kaptArgs[OPTION_GENERATED] = "javax.annotation.GeneratedBlerg"
|
||||
}.compile()
|
||||
assertThat(result.messages).contains(
|
||||
"Invalid option value for $OPTION_GENERATED",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableProguardRulesGenerating() {
|
||||
val result = prepareCompilation(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Foo(val a: Int)
|
||||
""",
|
||||
),
|
||||
).apply {
|
||||
kaptArgs[OPTION_GENERATE_PROGUARD_RULES] = "false"
|
||||
}.compile()
|
||||
assertThat(result.generatedFiles.filter { it.endsWith(".pro") }).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleErrors() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class Class1(private var a: Int, private var b: Int)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class Class2(private var c: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("property a is not visible")
|
||||
assertThat(result.messages).contains("property c is not visible")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun extendPlatformType() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.util.Date
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ExtendsPlatformClass(var a: Int) : Date()
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.messages).contains("supertype java.util.Date is not a Kotlin type")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun extendJavaType() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.kotlin.codegen.JavaSuperclass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ExtendsJavaType(var b: Int) : JavaSuperclass()
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages)
|
||||
.contains("supertype com.squareup.moshi.kotlin.codegen.JavaSuperclass is not a Kotlin type")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonFieldApplicableQualifier() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||
import kotlin.annotation.Retention
|
||||
import kotlin.annotation.Target
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(PROPERTY)
|
||||
@JsonQualifier
|
||||
annotation class UpperCase
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ClassWithQualifier(@UpperCase val a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
// We instantiate directly so doesn't need to be FIELD
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonRuntimeQualifier() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import kotlin.annotation.AnnotationRetention.BINARY
|
||||
import kotlin.annotation.AnnotationTarget.FIELD
|
||||
import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||
import kotlin.annotation.Retention
|
||||
import kotlin.annotation.Target
|
||||
|
||||
@Retention(BINARY)
|
||||
@Target(PROPERTY, FIELD)
|
||||
@JsonQualifier
|
||||
annotation class UpperCase
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ClassWithQualifier(@UpperCase val a: Int)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("JsonQualifier @UpperCase must have RUNTIME retention")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TypeAliases with the same backing type should share the same adapter`() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
typealias FirstName = String
|
||||
typealias LastName = String
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Person(val firstName: FirstName, val lastName: LastName, val hairColor: String)
|
||||
""",
|
||||
),
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
|
||||
|
||||
// We're checking here that we only generate one `stringAdapter` that's used for both the
|
||||
// regular string properties as well as the the aliased ones.
|
||||
val adapterClass = result.classLoader.loadClass("PersonJsonAdapter").kotlin
|
||||
assertThat(adapterClass.declaredMemberProperties.map { it.returnType }).containsExactly(
|
||||
JsonReader.Options::class.createType(),
|
||||
JsonAdapter::class.parameterizedBy(String::class),
|
||||
)
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
object NestedType {
|
||||
@JsonQualifier
|
||||
annotation class NestedQualifier
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NestedSimple(@NestedQualifier val firstName: String)
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
result.generatedFiles.filter { it.extension == "pro" }.forEach { generatedFile ->
|
||||
when (generatedFile.nameWithoutExtension) {
|
||||
"moshi-testPackage.Aliases" -> assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-if class testPackage.Aliases
|
||||
-keepnames class testPackage.Aliases
|
||||
-if class testPackage.Aliases
|
||||
-keep class testPackage.AliasesJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi);
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"moshi-testPackage.Simple" -> assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-if class testPackage.Simple
|
||||
-keepnames class testPackage.Simple
|
||||
-if class testPackage.Simple
|
||||
-keep class testPackage.SimpleJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi);
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"moshi-testPackage.Generic" -> assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-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(),
|
||||
)
|
||||
|
||||
"moshi-testPackage.UsingQualifiers" -> {
|
||||
assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-if class testPackage.UsingQualifiers
|
||||
-keepnames class testPackage.UsingQualifiers
|
||||
-if class testPackage.UsingQualifiers
|
||||
-keep class testPackage.UsingQualifiersJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi);
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
"moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-if class testPackage.MixedTypes
|
||||
-keepnames class testPackage.MixedTypes
|
||||
-if class testPackage.MixedTypes
|
||||
-keep class testPackage.MixedTypesJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi);
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"moshi-testPackage.DefaultParams" -> assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-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,kotlin.jvm.internal.DefaultConstructorMarker);
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"moshi-testPackage.Complex" -> {
|
||||
assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-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[]);
|
||||
}
|
||||
-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,kotlin.jvm.internal.DefaultConstructorMarker);
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
"moshi-testPackage.MultipleMasks" -> assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-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(),
|
||||
)
|
||||
|
||||
"moshi-testPackage.NestedType.NestedSimple" -> {
|
||||
assertThat(generatedFile.readText()).contains(
|
||||
"""
|
||||
-if class testPackage.NestedType${'$'}NestedSimple
|
||||
-keepnames class testPackage.NestedType${'$'}NestedSimple
|
||||
-if class testPackage.NestedType${'$'}NestedSimple
|
||||
-keep class testPackage.NestedType_NestedSimpleJsonAdapter {
|
||||
public <init>(com.squareup.moshi.Moshi);
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
else -> error("Unexpected proguard file! ${generatedFile.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation {
|
||||
return KotlinCompilation()
|
||||
.apply {
|
||||
workingDir = temporaryFolder.root
|
||||
annotationProcessors = listOf(JsonClassCodegenProcessor())
|
||||
inheritClassPath = true
|
||||
sources = sourceFiles.asList()
|
||||
verbose = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun compile(vararg sourceFiles: SourceFile): JvmCompilationResult {
|
||||
return prepareCompilation(*sourceFiles).compile()
|
||||
}
|
||||
|
||||
private fun KClassifier.parameterizedBy(vararg types: KClass<*>): KType {
|
||||
return parameterizedBy(*types.map { it.createType() }.toTypedArray())
|
||||
}
|
||||
|
||||
private fun KClassifier.parameterizedBy(vararg types: KType): KType {
|
||||
return createType(
|
||||
types.map { it.asProjection() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun KType.asProjection(variance: KVariance? = INVARIANT): KTypeProjection {
|
||||
return KTypeProjection(variance, this)
|
||||
}
|
||||
}
|
@@ -1,17 +1,14 @@
|
||||
import Build_gradle.TestMode.KAPT
|
||||
import Build_gradle.TestMode.KSP
|
||||
import Build_gradle.TestMode.REFLECT
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("kapt") apply false
|
||||
id("com.google.devtools.ksp") apply false
|
||||
}
|
||||
|
||||
enum class TestMode {
|
||||
REFLECT,
|
||||
KAPT,
|
||||
KSP,
|
||||
}
|
||||
|
||||
@@ -24,11 +21,6 @@ when (testMode) {
|
||||
REFLECT -> {
|
||||
// Do nothing!
|
||||
}
|
||||
|
||||
KAPT -> {
|
||||
apply(plugin = "org.jetbrains.kotlin.kapt")
|
||||
}
|
||||
|
||||
KSP -> {
|
||||
apply(plugin = "com.google.devtools.ksp")
|
||||
}
|
||||
@@ -53,11 +45,6 @@ dependencies {
|
||||
REFLECT -> {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
KAPT -> {
|
||||
"kaptTest"(project(":moshi-kotlin-codegen"))
|
||||
}
|
||||
|
||||
KSP -> {
|
||||
"kspTest"(project(":moshi-kotlin-codegen"))
|
||||
}
|
||||
|
@@ -1,17 +1,14 @@
|
||||
import Build_gradle.TestMode.KAPT
|
||||
import Build_gradle.TestMode.KSP
|
||||
import Build_gradle.TestMode.REFLECT
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("kapt") apply false
|
||||
id("com.google.devtools.ksp") apply false
|
||||
}
|
||||
|
||||
enum class TestMode {
|
||||
REFLECT,
|
||||
KAPT,
|
||||
KSP,
|
||||
}
|
||||
|
||||
@@ -25,11 +22,6 @@ when (testMode) {
|
||||
// Default to KSP. This is a CI-only thing
|
||||
apply(plugin = "com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
KAPT -> {
|
||||
apply(plugin = "org.jetbrains.kotlin.kapt")
|
||||
}
|
||||
|
||||
KSP -> {
|
||||
apply(plugin = "com.google.devtools.ksp")
|
||||
}
|
||||
@@ -55,11 +47,6 @@ dependencies {
|
||||
// Default to KSP in this case, this is a CI-only thing
|
||||
"kspTest"(project(":moshi-kotlin-codegen"))
|
||||
}
|
||||
|
||||
KAPT -> {
|
||||
"kaptTest"(project(":moshi-kotlin-codegen"))
|
||||
}
|
||||
|
||||
KSP -> {
|
||||
"kspTest"(project(":moshi-kotlin-codegen"))
|
||||
}
|
||||
|
Reference in New Issue
Block a user