diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 403122e..32514dd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- kotlin-test-mode: [ 'REFLECT', 'KSP', 'KAPT' ]
+ kotlin-test-mode: [ 'REFLECT', 'KSP' ]
steps:
- name: Checkout
diff --git a/README.md b/README.md
index 782a711..b8d7149 100644
--- a/README.md
+++ b/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 {
```
-
- Kapt
-
-```xml
-
- com.squareup.moshi
- moshi-kotlin-codegen
- 1.15.1
- provided
-
-```
-
-```kotlin
-kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
-```
-
-
#### 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
diff --git a/build.gradle.kts b/build.gradle.kts
index 916be08..14ce570 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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 {
diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts
index 8fad6dc..fa039ed 100644
--- a/examples/build.gradle.kts
+++ b/examples/build.gradle.kts
@@ -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"))
diff --git a/gradle.properties b/gradle.properties
index 4d3fb2a..0965822 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -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
diff --git a/moshi-kotlin-codegen/build.gradle.kts b/moshi-kotlin-codegen/build.gradle.kts
index 4dd3ba8..482bbde 100644
--- a/moshi-kotlin-codegen/build.gradle.kts
+++ b/moshi-kotlin-codegen/build.gradle.kts
@@ -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().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().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 {
configure(KotlinJvm(javadocJar = None()))
}
diff --git a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/AppliedType.kt b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/AppliedType.kt
deleted file mode 100644
index 26bd539..0000000
--- a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/AppliedType.kt
+++ /dev/null
@@ -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` 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 = LinkedHashSet(),
- ): LinkedHashSet {
- 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)
- }
- }
-}
diff --git a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessor.kt b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessor.kt
deleted file mode 100644
index 0050b9f..0000000
--- a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessor.kt
+++ /dev/null
@@ -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 = setOf(annotation.canonicalName)
-
- override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
-
- override fun getSupportedOptions(): Set = 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, 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()
- 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)
-}
diff --git a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/MoshiCachedClassInspector.kt b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/MoshiCachedClassInspector.kt
deleted file mode 100644
index f893921..0000000
--- a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/MoshiCachedClassInspector.kt
+++ /dev/null
@@ -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 { 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()
- private val kmClassToSpecCache = TreeMap(KmClassComparator)
- private val metadataToKmClassCache = mutableMapOf()
-
- 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))
- }
- }
-}
diff --git a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/metadata.kt b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/metadata.kt
deleted file mode 100644
index 23a7a7f..0000000
--- a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/metadata.kt
+++ /dev/null
@@ -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.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()
- 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()?.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()
-
- val resolvedTypes = mutableListOf()
- 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
- // class Bar
- //
- // We will store {Foo : {T : [String]}}.
- //
- // Then when we look at Bar 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()
- .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) { it.enclosingElement }
- .filterIsInstance()
- .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)
-
-private fun resolveTypeArgs(
- targetClass: ClassName,
- propertyType: TypeName,
- resolvedTypes: List,
- allowedTypeVars: Set,
- 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,
- currentClass: ClassName,
- resolvedTypes: List,
-): Map {
- val result = mutableMapOf()
- 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?.qualifiers(
- messager: Messager,
- elements: Elements,
-): Set {
- 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?.jsonName(): String? {
- if (this == null) return null
- return filter { it.typeName == JSON }.firstNotNullOfOrNull { annotation ->
- annotation.jsonName()
- }
-}
-
-private fun List?.jsonIgnore(): Boolean {
- if (this == null) return false
- return filter { it.typeName == JSON }.firstNotNullOfOrNull { annotation ->
- annotation.jsonIgnore()
- } ?: false
-}
-
-private fun AnnotationSpec.jsonName(): String? {
- return elementValue("name").takeUnless { it == Json.UNSET_NAME }
-}
-
-private fun AnnotationSpec.jsonIgnore(): Boolean {
- return elementValue("ignore") ?: false
-}
-
-private fun AnnotationSpec.elementValue(name: String): T? {
- val mirror = requireNotNull(tag()) {
- "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 Sequence<*>.cast(): Sequence {
- return map {
- @Suppress("UNCHECKED_CAST")
- it as E
- }
-}
diff --git a/moshi-kotlin-codegen/src/main/resources/META-INF/gradle/incremental.annotation.processors b/moshi-kotlin-codegen/src/main/resources/META-INF/gradle/incremental.annotation.processors
deleted file mode 100644
index 6605eb3..0000000
--- a/moshi-kotlin-codegen/src/main/resources/META-INF/gradle/incremental.annotation.processors
+++ /dev/null
@@ -1 +0,0 @@
-com.squareup.moshi.kotlin.codegen.apt.JsonClassCodegenProcessor,ISOLATING
diff --git a/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JavaSuperclass.java b/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JavaSuperclass.java
index ba97416..7b3ca30 100644
--- a/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JavaSuperclass.java
+++ b/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JavaSuperclass.java
@@ -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;
}
diff --git a/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessorTest.kt b/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessorTest.kt
deleted file mode 100644
index b320f51..0000000
--- a/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessorTest.kt
+++ /dev/null
@@ -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(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)
-
- @JsonClass(generateAdapter = true)
- data class DefaultParams(val firstName: String = "")
-
- @JsonClass(generateAdapter = true)
- data class Complex(val firstName: FirstName = "", @MyQualifier val names: MutableList, 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 (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 (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 (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 (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 (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 (com.squareup.moshi.Moshi);
- }
- -if class testPackage.DefaultParams
- -keepnames class kotlin.jvm.internal.DefaultConstructorMarker
- -if class testPackage.DefaultParams
- -keepclassmembers class testPackage.DefaultParams {
- public synthetic (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 (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 (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 (com.squareup.moshi.Moshi);
- }
- -if class testPackage.MultipleMasks
- -keepnames class kotlin.jvm.internal.DefaultConstructorMarker
- -if class testPackage.MultipleMasks
- -keepclassmembers class testPackage.MultipleMasks {
- public synthetic (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 (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)
- }
-}
diff --git a/moshi-kotlin-tests/build.gradle.kts b/moshi-kotlin-tests/build.gradle.kts
index c16cb09..29aae33 100644
--- a/moshi-kotlin-tests/build.gradle.kts
+++ b/moshi-kotlin-tests/build.gradle.kts
@@ -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"))
}
diff --git a/moshi-kotlin-tests/codegen-only/build.gradle.kts b/moshi-kotlin-tests/codegen-only/build.gradle.kts
index 5fc56c0..f08385e 100644
--- a/moshi-kotlin-tests/codegen-only/build.gradle.kts
+++ b/moshi-kotlin-tests/codegen-only/build.gradle.kts
@@ -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"))
}