mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 15:39: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:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
kotlin-test-mode: [ 'REFLECT', 'KSP', 'KAPT' ]
|
kotlin-test-mode: [ 'REFLECT', 'KSP' ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- 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
|
#### 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
|
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:
|
each class that you want to encode as JSON:
|
||||||
|
|
||||||
@@ -1109,23 +1109,6 @@ dependencies {
|
|||||||
```
|
```
|
||||||
</details>
|
</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
|
#### Limitations
|
||||||
|
|
||||||
If your Kotlin class has a superclass, it must also be a Kotlin class. Neither reflection or codegen
|
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/
|
[okhttp]: https://github.com/square/okhttp/
|
||||||
[gson]: https://github.com/google/gson/
|
[gson]: https://github.com/google/gson/
|
||||||
[javadoc]: https://square.github.io/moshi/1.x/moshi/
|
[javadoc]: https://square.github.io/moshi/1.x/moshi/
|
||||||
[kapt]: https://kotlinlang.org/docs/reference/kapt.html
|
|
||||||
[ksp]: https://github.com/google/ksp
|
[ksp]: https://github.com/google/ksp
|
||||||
|
@@ -27,6 +27,7 @@ plugins {
|
|||||||
alias(libs.plugins.dokka) apply false
|
alias(libs.plugins.dokka) apply false
|
||||||
alias(libs.plugins.spotless)
|
alias(libs.plugins.spotless)
|
||||||
alias(libs.plugins.japicmp) apply false
|
alias(libs.plugins.japicmp) apply false
|
||||||
|
alias(libs.plugins.ksp) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
@@ -2,11 +2,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("kapt")
|
alias(libs.plugins.ksp)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
kapt(project(":moshi-kotlin-codegen"))
|
ksp(project(":moshi-kotlin-codegen"))
|
||||||
compileOnly(libs.jsr305)
|
compileOnly(libs.jsr305)
|
||||||
implementation(project(":moshi"))
|
implementation(project(":moshi"))
|
||||||
implementation(project(":moshi-adapters"))
|
implementation(project(":moshi-adapters"))
|
||||||
|
@@ -1,4 +1,2 @@
|
|||||||
# Memory for Dokka https://github.com/Kotlin/dokka/issues/1405
|
# Memory for Dokka https://github.com/Kotlin/dokka/issues/1405
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
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.JavadocJar.None
|
||||||
import com.vanniktech.maven.publish.KotlinJvm
|
import com.vanniktech.maven.publish.KotlinJvm
|
||||||
import com.vanniktech.maven.publish.MavenPublishBaseExtension
|
import com.vanniktech.maven.publish.MavenPublishBaseExtension
|
||||||
@@ -8,7 +7,6 @@ plugins {
|
|||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
id("com.vanniktech.maven.publish.base")
|
id("com.vanniktech.maven.publish.base")
|
||||||
alias(libs.plugins.mavenShadow)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile>().configureEach {
|
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 {
|
dependencies {
|
||||||
implementation(project(":moshi"))
|
implementation(project(":moshi"))
|
||||||
shade(libs.kotlinxMetadata) {
|
|
||||||
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
|
|
||||||
}
|
|
||||||
api(libs.kotlinpoet)
|
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.kotlinpoet.ksp)
|
||||||
implementation(libs.guava)
|
implementation(libs.guava)
|
||||||
implementation(libs.asm)
|
implementation(libs.asm)
|
||||||
@@ -83,26 +55,6 @@ dependencies {
|
|||||||
testImplementation(libs.kotlinCompileTesting)
|
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<MavenPublishBaseExtension> {
|
||||||
configure(KotlinJvm(javadocJar = None()))
|
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;
|
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 class JavaSuperclass {
|
||||||
public int a = 1;
|
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.KSP
|
||||||
import Build_gradle.TestMode.REFLECT
|
import Build_gradle.TestMode.REFLECT
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("kapt") apply false
|
|
||||||
id("com.google.devtools.ksp") apply false
|
id("com.google.devtools.ksp") apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class TestMode {
|
enum class TestMode {
|
||||||
REFLECT,
|
REFLECT,
|
||||||
KAPT,
|
|
||||||
KSP,
|
KSP,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,11 +21,6 @@ when (testMode) {
|
|||||||
REFLECT -> {
|
REFLECT -> {
|
||||||
// Do nothing!
|
// Do nothing!
|
||||||
}
|
}
|
||||||
|
|
||||||
KAPT -> {
|
|
||||||
apply(plugin = "org.jetbrains.kotlin.kapt")
|
|
||||||
}
|
|
||||||
|
|
||||||
KSP -> {
|
KSP -> {
|
||||||
apply(plugin = "com.google.devtools.ksp")
|
apply(plugin = "com.google.devtools.ksp")
|
||||||
}
|
}
|
||||||
@@ -53,11 +45,6 @@ dependencies {
|
|||||||
REFLECT -> {
|
REFLECT -> {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
KAPT -> {
|
|
||||||
"kaptTest"(project(":moshi-kotlin-codegen"))
|
|
||||||
}
|
|
||||||
|
|
||||||
KSP -> {
|
KSP -> {
|
||||||
"kspTest"(project(":moshi-kotlin-codegen"))
|
"kspTest"(project(":moshi-kotlin-codegen"))
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
import Build_gradle.TestMode.KAPT
|
|
||||||
import Build_gradle.TestMode.KSP
|
import Build_gradle.TestMode.KSP
|
||||||
import Build_gradle.TestMode.REFLECT
|
import Build_gradle.TestMode.REFLECT
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("kapt") apply false
|
|
||||||
id("com.google.devtools.ksp") apply false
|
id("com.google.devtools.ksp") apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class TestMode {
|
enum class TestMode {
|
||||||
REFLECT,
|
REFLECT,
|
||||||
KAPT,
|
|
||||||
KSP,
|
KSP,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,11 +22,6 @@ when (testMode) {
|
|||||||
// Default to KSP. This is a CI-only thing
|
// Default to KSP. This is a CI-only thing
|
||||||
apply(plugin = "com.google.devtools.ksp")
|
apply(plugin = "com.google.devtools.ksp")
|
||||||
}
|
}
|
||||||
|
|
||||||
KAPT -> {
|
|
||||||
apply(plugin = "org.jetbrains.kotlin.kapt")
|
|
||||||
}
|
|
||||||
|
|
||||||
KSP -> {
|
KSP -> {
|
||||||
apply(plugin = "com.google.devtools.ksp")
|
apply(plugin = "com.google.devtools.ksp")
|
||||||
}
|
}
|
||||||
@@ -55,11 +47,6 @@ dependencies {
|
|||||||
// Default to KSP in this case, this is a CI-only thing
|
// Default to KSP in this case, this is a CI-only thing
|
||||||
"kspTest"(project(":moshi-kotlin-codegen"))
|
"kspTest"(project(":moshi-kotlin-codegen"))
|
||||||
}
|
}
|
||||||
|
|
||||||
KAPT -> {
|
|
||||||
"kaptTest"(project(":moshi-kotlin-codegen"))
|
|
||||||
}
|
|
||||||
|
|
||||||
KSP -> {
|
KSP -> {
|
||||||
"kspTest"(project(":moshi-kotlin-codegen"))
|
"kspTest"(project(":moshi-kotlin-codegen"))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user