mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Support direct annotation instantiation in code gen on Kotlin 1.6 (#1390)
This commit is contained in:
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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())
|
||||
|
@@ -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.
|
||||
|
@@ -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")
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user