From b7f3aff847a2f1b94c25d5f05940cce983f7c8c4 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 2 Jun 2024 23:00:47 -0400 Subject: [PATCH 1/4] Remove KAPT support --- .github/workflows/build.yml | 2 +- README.md | 20 +- examples/build.gradle.kts | 4 +- gradle.properties | 2 - moshi-kotlin-codegen/build.gradle.kts | 48 -- .../moshi/kotlin/codegen/apt/AppliedType.kt | 65 --- .../codegen/apt/JsonClassCodegenProcessor.kt | 186 ------ .../codegen/apt/MoshiCachedClassInspector.kt | 56 -- .../moshi/kotlin/codegen/apt/metadata.kt | 539 ------------------ .../gradle/incremental.annotation.processors | 1 - moshi-kotlin-tests/build.gradle.kts | 13 - .../codegen-only/build.gradle.kts | 13 - 12 files changed, 4 insertions(+), 945 deletions(-) delete mode 100644 moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/AppliedType.kt delete mode 100644 moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessor.kt delete mode 100644 moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/MoshiCachedClassInspector.kt delete mode 100644 moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/apt/metadata.kt delete mode 100644 moshi-kotlin-codegen/src/main/resources/META-INF/gradle/incremental.annotation.processors diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 403122e49..32514ddb5 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 782a711bd..b8d71491a 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/examples/build.gradle.kts b/examples/build.gradle.kts index 8fad6dcc7..fa039edde 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 4d3fb2a50..096582208 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 4dd3ba8e9..482bbdeae 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 26bd5392d..000000000 --- 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 0050b9f26..000000000 --- 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 f89392112..000000000 --- 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 23a7a7fdd..000000000 --- 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 6605eb357..000000000 --- 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-tests/build.gradle.kts b/moshi-kotlin-tests/build.gradle.kts index c16cb099b..29aae33ab 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 5fc56c010..f08385e7f 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")) } From 05c92b8bb4b169bfdc704f7c9d4db4853eaea0dd Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 2 Jun 2024 23:02:39 -0400 Subject: [PATCH 2/4] Stick KSP on the root classpath --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 916be086d..14ce57041 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 { From d8a4bdb58d3a293fcbe962c909783838a84c2c6c Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 2 Jun 2024 23:11:11 -0400 Subject: [PATCH 3/4] Another --- .../apt/JsonClassCodegenProcessorTest.kt | 817 ------------------ 1 file changed, 817 deletions(-) delete mode 100644 moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/apt/JsonClassCodegenProcessorTest.kt 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 b320f51db..000000000 --- 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) - } -} From 4df17f5f07ac4405e23dce2207b41fd02c847c38 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 2 Jun 2024 23:17:11 -0400 Subject: [PATCH 4/4] Another --- .../com/squareup/moshi/kotlin/codegen/JavaSuperclass.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ba97416e5..7b3ca30d2 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; }