Support direct annotation instantiation in code gen on Kotlin 1.6 (#1390)

This commit is contained in:
Zac Sweers
2021-10-22 13:43:09 -04:00
committed by GitHub
parent a9eaa849e5
commit b8fbe38118
17 changed files with 396 additions and 171 deletions

View File

@@ -4,13 +4,24 @@ on: [push, pull_request]
jobs:
build:
name: 'Java ${{ matrix.java-version }} | KSP ${{ matrix.use-ksp }}'
name: 'Java ${{ matrix.java-version }} | Kotlin ${{ matrix.kotlin-version }} | KSP ${{ matrix.use-ksp }}'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
use-ksp: [ true, false ]
kotlin-version: [ '1.5.31', '1.6.0-RC' ]
ksp-version: [ '1.5.31-1.0.0', '1.6.0-RC-1.0.0' ]
exclude:
- kotlin-version: '1.5.31'
ksp-version: '1.6.0-RC-1.0.0'
- kotlin-version: '1.6.0-RC'
ksp-version: '1.5.31-1.0.0'
env:
MOSHI_KOTLIN_VERSION: '${{ matrix.kotlin-version }}'
MOSHI_KSP_VERSION: '${{ matrix.ksp-version }}'
steps:
- name: Checkout
@@ -36,8 +47,8 @@ jobs:
java-version: '17'
- name: Test
run: ./gradlew build check --stacktrace -PuseKsp=${{ matrix.use-ksp }}
run: ./gradlew build check --stacktrace -PuseKsp=${{ matrix.use-ksp }} -PkotlinVersion=${{ matrix.kotlin-version }}
- name: Publish (default branch only)
if: github.repository == 'square/moshi' && github.ref == 'refs/heads/master'
if: github.repository == 'square/moshi' && github.ref == 'refs/heads/master' && matrix.kotlin-version == '1.5.31' && matrix.use-ksp == 'false'
run: ./gradlew publish -PmavenCentralUsername=${{ secrets.SONATYPE_NEXUS_USERNAME }} -PmavenCentralPassword=${{ secrets.SONATYPE_NEXUS_PASSWORD }} --stacktrace

View File

@@ -23,7 +23,12 @@ import java.net.URL
buildscript {
dependencies {
classpath(kotlin("gradle-plugin", version = libs.versions.kotlin.get()))
val kotlinVersion = System.getenv("MOSHI_KOTLIN_VERSION")
?: libs.versions.kotlin.get()
val kspVersion = System.getenv("MOSHI_KSP_VERSION")
?: libs.versions.ksp.get()
classpath(kotlin("gradle-plugin", version = kotlinVersion))
classpath("com.google.devtools.ksp:symbol-processing-gradle-plugin:$kspVersion")
// https://github.com/melix/japicmp-gradle-plugin/issues/36
classpath("com.google.guava:guava:28.2-jre")
}
@@ -125,8 +130,9 @@ subprojects {
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
@Suppress("SuspiciousCollectionReassignment")
freeCompilerArgs += listOf("-progressive")
// TODO re-enable when no longer supporting multiple kotlin versions
// @Suppress("SuspiciousCollectionReassignment")
// freeCompilerArgs += listOf("-progressive")
jvmTarget = libs.versions.jvmTarget.get()
}
}

View File

@@ -18,7 +18,7 @@ gjf = "1.11.0"
jvmTarget = "1.8"
kotlin = "1.5.31"
kotlinCompileTesting = "1.4.4"
kotlinpoet = "1.10.1"
kotlinpoet = "1.10.2"
ksp = "1.5.31-1.0.0"
ktlint = "0.41.0"

View File

@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
alias(libs.plugins.ksp)
id("com.google.devtools.ksp")
id("com.vanniktech.maven.publish")
alias(libs.plugins.mavenShadow)
}

View File

@@ -185,15 +185,19 @@ internal class AdapterGenerator(
result.addAnnotation(COMMON_SUPPRESS)
result.addType(generatedAdapter)
val proguardConfig = if (generateProguardRules) {
generatedAdapter.createProguardRule()
generatedAdapter.createProguardRule(target.instantiateAnnotations)
} else {
null
}
return PreparedAdapter(result.build(), proguardConfig)
}
private fun TypeSpec.createProguardRule(): ProguardConfig {
val adapterProperties = propertySpecs
private fun TypeSpec.createProguardRule(instantiateAnnotations: Boolean): ProguardConfig {
val adapterProperties = if (instantiateAnnotations) {
// Don't need to do anything special if we instantiate them directly!
emptySet()
} else {
propertySpecs
.asSequence()
.filter { prop ->
prop.type.rawType() == JsonAdapter::class.asClassName()
@@ -205,6 +209,7 @@ internal class AdapterGenerator(
qualifiers = prop.annotations.mapTo(mutableSetOf()) { it.typeName.rawType() }
)
}
}
val adapterConstructorParams = when (requireNotNull(primaryConstructor).parameters.size) {
1 -> listOf(CN_MOSHI.reflectionName())

View File

@@ -17,6 +17,7 @@ package com.squareup.moshi.kotlin.codegen.api
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.NameAllocator
@@ -29,6 +30,7 @@ import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.joinToCode
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Types
import java.util.Locale
@@ -36,7 +38,8 @@ import java.util.Locale
/** A JsonAdapter that can be used to encode and decode a particular field. */
internal data class DelegateKey(
private val type: TypeName,
private val jsonQualifiers: List<AnnotationSpec>
private val jsonQualifiers: List<AnnotationSpec>,
private val instantiateAnnotations: Boolean
) {
val nullable get() = type.isNullable
@@ -60,8 +63,12 @@ internal data class DelegateKey(
moshiParameter,
typeRenderer.render(type)
)
val (initializerString, args) = when {
jsonQualifiers.isEmpty() -> ", %M()" to arrayOf(MemberName("kotlin.collections", "emptySet"))
instantiateAnnotations -> {
", setOf(%L)" to arrayOf(jsonQualifiers.map { it.asInstantiationExpression() }.joinToCode())
}
else -> {
", %T.getFieldJsonQualifierAnnotations(javaClass, " +
"%S)" to arrayOf(Types::class.asTypeName(), adapterName)
@@ -70,12 +77,25 @@ internal data class DelegateKey(
val finalArgs = arrayOf(*standardArgs, *args, propertyName)
return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
.addAnnotations(jsonQualifiers)
.apply {
if (!instantiateAnnotations) {
addAnnotations(jsonQualifiers)
}
}
.initializer("%N.adapter(%L$initializerString, %S)", *finalArgs)
.build()
}
}
private fun AnnotationSpec.asInstantiationExpression(): CodeBlock {
// <Type>(args)
return CodeBlock.of(
"%T(%L)",
typeName,
members.joinToCode()
)
}
/**
* Returns a suggested variable name derived from a list of type names. This just concatenates,
* yielding types like MapOfStringLong.

View File

@@ -36,6 +36,14 @@ internal object Options {
*/
const val OPTION_GENERATE_PROGUARD_RULES: String = "moshi.generateProguardRules"
/**
* This boolean processing option controls whether or not Moshi will directly instantiate
* JsonQualifier annotations in Kotlin 1.6+. Note that this is enabled by default in Kotlin 1.6
* but can be disabled to restore the legacy behavior of storing annotations on generated adapter
* fields and looking them up reflectively.
*/
const val OPTION_INSTANTIATE_ANNOTATIONS: String = "moshi.instantiateAnnotations"
val POSSIBLE_GENERATED_NAMES = arrayOf(
ClassName("javax.annotation.processing", "Generated"),
ClassName("javax.annotation", "Generated")

View File

@@ -26,7 +26,8 @@ internal data class TargetType(
val properties: Map<String, TargetProperty>,
val typeVariables: List<TypeVariableName>,
val isDataClass: Boolean,
val visibility: KModifier
val visibility: KModifier,
val instantiateAnnotations: Boolean
) {
init {

View File

@@ -23,6 +23,7 @@ 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
@@ -59,6 +60,7 @@ public class JsonClassCodegenProcessor : AbstractProcessor() {
private val annotation = JsonClass::class.java
private var generatedType: ClassName? = null
private var generateProguardRules: Boolean = true
private var instantiateAnnotations: Boolean = true
override fun getSupportedAnnotationTypes(): Set<String> = setOf(annotation.canonicalName)
@@ -76,6 +78,7 @@ public class JsonClassCodegenProcessor : AbstractProcessor() {
}
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
@@ -135,11 +138,18 @@ public class JsonClassCodegenProcessor : AbstractProcessor() {
element: TypeElement,
cachedClassInspector: MoshiCachedClassInspector
): AdapterGenerator? {
val type = targetType(messager, elements, types, element, cachedClassInspector) ?: return null
val type = targetType(
messager,
elements,
types,
element,
cachedClassInspector,
instantiateAnnotations
) ?: return null
val properties = mutableMapOf<String, PropertyGenerator>()
for (property in type.properties.values) {
val generator = property.generator(messager, element, elements)
val generator = property.generator(messager, element, elements, type.instantiateAnnotations)
if (generator != null) {
properties[property.name] = generator
}

View File

@@ -71,6 +71,7 @@ private val VISIBILITY_MODIFIERS = setOf(
KModifier.PROTECTED,
KModifier.PUBLIC
)
private val ANNOTATION_INSTANTIATION_MIN_VERSION = KotlinVersion(1, 6, 0)
private fun Collection<KModifier>.visibility(): KModifier {
return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC
@@ -122,7 +123,8 @@ internal fun targetType(
elements: Elements,
types: Types,
element: TypeElement,
cachedClassInspector: MoshiCachedClassInspector
cachedClassInspector: MoshiCachedClassInspector,
instantiateAnnotationsEnabled: Boolean
): TargetType? {
val typeMetadata = element.getAnnotation(Metadata::class.java)
if (typeMetadata == null) {
@@ -204,6 +206,11 @@ internal fun targetType(
}
}
val instantiateAnnotations = instantiateAnnotationsEnabled && run {
val (major, minor, patch) = typeMetadata.metadataVersion
val languageVersion = KotlinVersion(major, minor, patch)
languageVersion >= ANNOTATION_INSTANTIATION_MIN_VERSION
}
val kotlinApi = cachedClassInspector.toTypeSpec(kmClass)
val typeVariables = kotlinApi.typeVariables
val appliedType = AppliedType.get(element)
@@ -319,7 +326,8 @@ internal fun targetType(
properties = properties,
typeVariables = typeVariables,
isDataClass = KModifier.DATA in kotlinApi.modifiers,
visibility = resolvedVisibility
visibility = resolvedVisibility,
instantiateAnnotations = instantiateAnnotations
)
}
@@ -418,7 +426,8 @@ private val TargetProperty.isVisible: Boolean
internal fun TargetProperty.generator(
messager: Messager,
sourceElement: TypeElement,
elements: Elements
elements: Elements,
instantiateAnnotations: Boolean
): PropertyGenerator? {
if (isTransient) {
if (!hasDefault) {
@@ -429,7 +438,7 @@ internal fun TargetProperty.generator(
)
return null
}
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
return PropertyGenerator(this, DelegateKey(type, emptyList(), instantiateAnnotations), true)
}
if (!isVisible) {
@@ -452,6 +461,7 @@ internal fun TargetProperty.generator(
// 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(
@@ -460,6 +470,7 @@ internal fun TargetProperty.generator(
)
}
}
if (!instantiateAnnotations) {
annotationElement.getAnnotation(Target::class.java)?.let {
if (ElementType.FIELD !in it.value) {
messager.printMessage(
@@ -469,6 +480,7 @@ internal fun TargetProperty.generator(
}
}
}
}
val jsonQualifierSpecs = qualifiers.map {
it.toBuilder()
@@ -478,7 +490,7 @@ internal fun TargetProperty.generator(
return PropertyGenerator(
this,
DelegateKey(type, jsonQualifierSpecs)
DelegateKey(type, jsonQualifierSpecs, instantiateAnnotations)
)
}

View File

@@ -34,6 +34,7 @@ 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
@@ -63,6 +64,8 @@ private class JsonClassSymbolProcessor(
}
}
private val generateProguardRules = environment.options[OPTION_GENERATE_PROGUARD_RULES]?.toBooleanStrictOrNull() ?: true
private val instantiateAnnotations = (environment.options[OPTION_INSTANTIATE_ANNOTATIONS]?.toBooleanStrictOrNull() ?: true) &&
environment.kotlinVersion.isAtLeast(1, 6)
override fun process(resolver: Resolver): List<KSAnnotated> {
val generatedAnnotation = generatedOption?.let {
@@ -122,11 +125,11 @@ private class JsonClassSymbolProcessor(
resolver: Resolver,
originalType: KSDeclaration,
): AdapterGenerator? {
val type = targetType(originalType, resolver, logger) ?: return null
val type = targetType(originalType, resolver, logger, instantiateAnnotations) ?: return null
val properties = mutableMapOf<String, PropertyGenerator>()
for (property in type.properties.values) {
val generator = property.generator(logger, resolver, originalType)
val generator = property.generator(logger, resolver, originalType, type.instantiateAnnotations)
if (generator != null) {
properties[property.name] = generator
}

View File

@@ -69,12 +69,12 @@ private fun addValueToBlock(value: Any, resolver: Resolver, member: CodeBlock.Bu
when (value) {
is List<*> -> {
// Array type
member.add("[⇥⇥")
member.add("arrayOf(⇥⇥")
value.forEachIndexed { index, innerValue ->
if (index > 0) member.add(", ")
addValueToBlock(innerValue!!, resolver, member)
}
member.add("⇤⇤]")
member.add("⇤⇤)")
}
is KSType -> {
val unwrapped = value.unwrapTypeAlias()

View File

@@ -28,17 +28,6 @@ import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
import com.squareup.moshi.kotlin.codegen.api.TargetProperty
import com.squareup.moshi.kotlin.codegen.api.rawType
private val VISIBILITY_MODIFIERS = setOf(
KModifier.INTERNAL,
KModifier.PRIVATE,
KModifier.PROTECTED,
KModifier.PUBLIC
)
internal fun Collection<KModifier>.visibility(): KModifier {
return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC
}
private val TargetProperty.isTransient get() = propertySpec.annotations.any { it.typeName == Transient::class.asClassName() }
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
private val TargetProperty.isVisible: Boolean
@@ -55,7 +44,8 @@ private val TargetProperty.isVisible: Boolean
internal fun TargetProperty.generator(
logger: KSPLogger,
resolver: Resolver,
originalType: KSDeclaration
originalType: KSDeclaration,
instantiateAnnotations: Boolean
): PropertyGenerator? {
if (isTransient) {
if (!hasDefault) {
@@ -65,7 +55,7 @@ internal fun TargetProperty.generator(
)
return null
}
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
return PropertyGenerator(this, DelegateKey(type, emptyList(), instantiateAnnotations), true)
}
if (!isVisible) {
@@ -93,6 +83,7 @@ internal fun TargetProperty.generator(
)
}
}
if (!instantiateAnnotations) {
annotationElement.findAnnotationWithType<Target>()?.let {
if (AnnotationTarget.FIELD !in it.allowedTargets) {
logger.error(
@@ -102,6 +93,7 @@ internal fun TargetProperty.generator(
}
}
}
}
val jsonQualifierSpecs = qualifiers.map {
it.toBuilder()
@@ -111,7 +103,7 @@ internal fun TargetProperty.generator(
return PropertyGenerator(
this,
DelegateKey(type, jsonQualifierSpecs)
DelegateKey(type, jsonQualifierSpecs, instantiateAnnotations)
)
}

View File

@@ -58,6 +58,7 @@ internal fun targetType(
type: KSDeclaration,
resolver: Resolver,
logger: KSPLogger,
instantiateAnnotations: Boolean
): TargetType? {
if (type !is KSClassDeclaration) {
logger.error("@JsonClass can't be applied to ${type.qualifiedName?.asString()}: must be a Kotlin class", type)
@@ -152,7 +153,8 @@ internal fun targetType(
properties = properties,
typeVariables = typeVariables,
isDataClass = Modifier.DATA in type.modifiers,
visibility = resolvedVisibility
visibility = resolvedVisibility,
instantiateAnnotations = instantiateAnnotations
)
}

View File

@@ -18,9 +18,9 @@ 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
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.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.SourceFile
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
@@ -28,6 +28,8 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
import kotlin.reflect.KType
@@ -38,7 +40,24 @@ 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 {
@RunWith(Parameterized::class)
class JsonClassCodegenProcessorTest(
private val languageVersion: String,
private val instantiateAnnotations: Boolean
) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "languageVersion={0},instantiateAnnotations={1}")
fun data(): Collection<Array<Any>> {
return listOf(
arrayOf("1.5", true),
arrayOf("1.6", true),
arrayOf("1.6", false)
)
}
}
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
@Test
@@ -455,8 +474,13 @@ class JsonClassCodegenProcessorTest {
"""
)
)
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
assertThat(result.messages).contains("JsonQualifier @UpperCase must support FIELD target")
} else {
// We instantiate directly!
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
}
}
@Test
@@ -665,7 +689,9 @@ class JsonClassCodegenProcessorTest {
}
""".trimIndent()
)
"moshi-testPackage.UsingQualifiers" -> assertThat(generatedFile.readText()).contains(
"moshi-testPackage.UsingQualifiers" -> {
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.UsingQualifiers
-keepnames class testPackage.UsingQualifiers
@@ -678,6 +704,19 @@ class JsonClassCodegenProcessorTest {
-keep @interface testPackage.MyQualifier
""".trimIndent()
)
} else {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.UsingQualifiers
-keepnames class testPackage.UsingQualifiers
-if class testPackage.UsingQualifiers
-keep class testPackage.UsingQualifiersJsonAdapter {
public <init>(com.squareup.moshi.Moshi);
}
""".trimIndent()
)
}
}
"moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.MixedTypes
@@ -704,7 +743,9 @@ class JsonClassCodegenProcessorTest {
}
""".trimIndent()
)
"moshi-testPackage.Complex" -> assertThat(generatedFile.readText()).contains(
"moshi-testPackage.Complex" -> {
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.Complex
-keepnames class testPackage.Complex
@@ -723,6 +764,25 @@ class JsonClassCodegenProcessorTest {
}
""".trimIndent()
)
} else {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.Complex
-keepnames class testPackage.Complex
-if class testPackage.Complex
-keep class testPackage.ComplexJsonAdapter {
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
}
-if class testPackage.Complex
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
-if class testPackage.Complex
-keepclassmembers class testPackage.Complex {
public synthetic <init>(java.lang.String,java.util.List,java.lang.Object,int,kotlin.jvm.internal.DefaultConstructorMarker);
}
""".trimIndent()
)
}
}
"moshi-testPackage.MultipleMasks" -> assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.MultipleMasks
@@ -739,7 +799,9 @@ class JsonClassCodegenProcessorTest {
}
""".trimIndent()
)
"moshi-testPackage.NestedType.NestedSimple" -> assertThat(generatedFile.readText()).contains(
"moshi-testPackage.NestedType.NestedSimple" -> {
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.NestedType${'$'}NestedSimple
-keepnames class testPackage.NestedType${'$'}NestedSimple
@@ -752,6 +814,19 @@ class JsonClassCodegenProcessorTest {
-keep @interface testPackage.NestedType${'$'}NestedQualifier
""".trimIndent()
)
} else {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.NestedType${'$'}NestedSimple
-keepnames class testPackage.NestedType${'$'}NestedSimple
-if class testPackage.NestedType${'$'}NestedSimple
-keep class testPackage.NestedType_NestedSimpleJsonAdapter {
public <init>(com.squareup.moshi.Moshi);
}
""".trimIndent()
)
}
}
else -> error("Unexpected proguard file! ${generatedFile.name}")
}
}
@@ -765,6 +840,8 @@ class JsonClassCodegenProcessorTest {
inheritClassPath = true
sources = sourceFiles.asList()
verbose = false
kotlincArguments = listOf("-language-version", languageVersion)
kaptArgs[OPTION_INSTANTIATE_ANNOTATIONS] = "$instantiateAnnotations"
}
}

View File

@@ -16,6 +16,7 @@
package com.squareup.moshi.kotlin.codegen.ksp
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.kotlin.codegen.api.Options
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.KotlinCompilation
@@ -30,15 +31,41 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
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 org.junit.runner.RunWith
import org.junit.runners.Parameterized
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
class JsonClassSymbolProcessorTest {
@RunWith(Parameterized::class)
class JsonClassSymbolProcessorTest(
private val languageVersion: String,
private val instantiateAnnotations: Boolean
) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "languageVersion={0},instantiateAnnotations={1}")
fun data(): Collection<Array<Any>> {
// TODO KSP doesn't recognize language version yet https://github.com/google/ksp/issues/611
// toe-holds for the future
val runtimeVersion = KotlinVersion.CURRENT
return mutableListOf<Array<Any>>().apply {
if (runtimeVersion.major != 1 || runtimeVersion.minor != 6) {
// True by default but still 1.5
// Can't test when running with 1.6 because lang version is still 1.6 per above comment
add(arrayOf("1.5", true))
}
// Redundant case, set to false but still 1.5
add(arrayOf("1.5", false))
if (runtimeVersion.major != 1 || runtimeVersion.minor != 5) {
// Happy case - 1.6 and true by default
// Can't test when running with 1.5 because lang version is still 1.5 per above comment
add(arrayOf("1.6", true))
}
// 1.6 but explicitly disabled
add(arrayOf("1.6", false))
}
}
}
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
@@ -490,8 +517,13 @@ class JsonClassSymbolProcessorTest {
"""
)
)
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
assertThat(result.messages).contains("JsonQualifier @UpperCase must support FIELD target")
} else {
// We instantiate directly!
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
}
}
@Test
@@ -704,7 +736,9 @@ class JsonClassSymbolProcessorTest {
}
""".trimIndent()
)
"moshi-testPackage.UsingQualifiers" -> assertThat(generatedFile.readText()).contains(
"moshi-testPackage.UsingQualifiers" -> {
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.UsingQualifiers
-keepnames class testPackage.UsingQualifiers
@@ -717,6 +751,19 @@ class JsonClassSymbolProcessorTest {
-keep @interface testPackage.MyQualifier
""".trimIndent()
)
} else {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.UsingQualifiers
-keepnames class testPackage.UsingQualifiers
-if class testPackage.UsingQualifiers
-keep class testPackage.UsingQualifiersJsonAdapter {
public <init>(com.squareup.moshi.Moshi);
}
""".trimIndent()
)
}
}
"moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.MixedTypes
@@ -743,7 +790,9 @@ class JsonClassSymbolProcessorTest {
}
""".trimIndent()
)
"moshi-testPackage.Complex" -> assertThat(generatedFile.readText()).contains(
"moshi-testPackage.Complex" -> {
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.Complex
-keepnames class testPackage.Complex
@@ -762,6 +811,25 @@ class JsonClassSymbolProcessorTest {
}
""".trimIndent()
)
} else {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.Complex
-keepnames class testPackage.Complex
-if class testPackage.Complex
-keep class testPackage.ComplexJsonAdapter {
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
}
-if class testPackage.Complex
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
-if class testPackage.Complex
-keepclassmembers class testPackage.Complex {
public synthetic <init>(java.lang.String,java.util.List,java.lang.Object,int,kotlin.jvm.internal.DefaultConstructorMarker);
}
""".trimIndent()
)
}
}
"moshi-testPackage.MultipleMasks" -> assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.MultipleMasks
@@ -778,7 +846,9 @@ class JsonClassSymbolProcessorTest {
}
""".trimIndent()
)
"moshi-testPackage.NestedType.NestedSimple" -> assertThat(generatedFile.readText()).contains(
"moshi-testPackage.NestedType.NestedSimple" -> {
if (languageVersion == "1.5" || !instantiateAnnotations) {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.NestedType${'$'}NestedSimple
-keepnames class testPackage.NestedType${'$'}NestedSimple
@@ -791,6 +861,19 @@ class JsonClassSymbolProcessorTest {
-keep @interface testPackage.NestedType${'$'}NestedQualifier
""".trimIndent()
)
} else {
assertThat(generatedFile.readText()).contains(
"""
-if class testPackage.NestedType${'$'}NestedSimple
-keepnames class testPackage.NestedType${'$'}NestedSimple
-if class testPackage.NestedType${'$'}NestedSimple
-keep class testPackage.NestedType_NestedSimpleJsonAdapter {
public <init>(com.squareup.moshi.Moshi);
}
""".trimIndent()
)
}
}
else -> error("Unexpected proguard file! ${generatedFile.name}")
}
}
@@ -805,20 +888,12 @@ class JsonClassSymbolProcessorTest {
sources = sourceFiles.asList()
verbose = false
kspIncremental = true // The default now
kotlincArguments = listOf("-language-version", languageVersion)
kspArgs[Options.OPTION_INSTANTIATE_ANNOTATIONS] = "$instantiateAnnotations"
}
}
private fun compile(vararg sourceFiles: SourceFile): KotlinCompilation.Result {
return prepareCompilation(*sourceFiles).compile()
}
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)
}
}

View File

@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
kotlin("kapt") apply false
alias(libs.plugins.ksp) apply false
id("com.google.devtools.ksp") apply false
}
val useKsp = hasProperty("useKsp")
@@ -34,11 +34,14 @@ tasks.withType<Test>().configureEach {
jvmArgs("--add-opens=java.base/java.io=ALL-UNNAMED")
}
val useWError = findProperty("kotlinLanguageVersion")?.toString()
?.startsWith("1.5")
?: false
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
allWarningsAsErrors = useWError
@Suppress("SuspiciousCollectionReassignment")
freeCompilerArgs += listOf(
"-Werror",
"-Xopt-in=kotlin.ExperimentalStdlibApi"
)
}