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:
|
jobs:
|
||||||
build:
|
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
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
use-ksp: [ true, false ]
|
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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -36,8 +47,8 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Test
|
- 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)
|
- 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
|
run: ./gradlew publish -PmavenCentralUsername=${{ secrets.SONATYPE_NEXUS_USERNAME }} -PmavenCentralPassword=${{ secrets.SONATYPE_NEXUS_PASSWORD }} --stacktrace
|
||||||
|
@@ -23,7 +23,12 @@ import java.net.URL
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
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
|
// https://github.com/melix/japicmp-gradle-plugin/issues/36
|
||||||
classpath("com.google.guava:guava:28.2-jre")
|
classpath("com.google.guava:guava:28.2-jre")
|
||||||
}
|
}
|
||||||
@@ -125,8 +130,9 @@ subprojects {
|
|||||||
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
|
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
|
||||||
tasks.withType<KotlinCompile>().configureEach {
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@Suppress("SuspiciousCollectionReassignment")
|
// TODO re-enable when no longer supporting multiple kotlin versions
|
||||||
freeCompilerArgs += listOf("-progressive")
|
// @Suppress("SuspiciousCollectionReassignment")
|
||||||
|
// freeCompilerArgs += listOf("-progressive")
|
||||||
jvmTarget = libs.versions.jvmTarget.get()
|
jvmTarget = libs.versions.jvmTarget.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ gjf = "1.11.0"
|
|||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
kotlin = "1.5.31"
|
kotlin = "1.5.31"
|
||||||
kotlinCompileTesting = "1.4.4"
|
kotlinCompileTesting = "1.4.4"
|
||||||
kotlinpoet = "1.10.1"
|
kotlinpoet = "1.10.2"
|
||||||
ksp = "1.5.31-1.0.0"
|
ksp = "1.5.31-1.0.0"
|
||||||
ktlint = "0.41.0"
|
ktlint = "0.41.0"
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
alias(libs.plugins.ksp)
|
id("com.google.devtools.ksp")
|
||||||
id("com.vanniktech.maven.publish")
|
id("com.vanniktech.maven.publish")
|
||||||
alias(libs.plugins.mavenShadow)
|
alias(libs.plugins.mavenShadow)
|
||||||
}
|
}
|
||||||
|
@@ -185,26 +185,31 @@ internal class AdapterGenerator(
|
|||||||
result.addAnnotation(COMMON_SUPPRESS)
|
result.addAnnotation(COMMON_SUPPRESS)
|
||||||
result.addType(generatedAdapter)
|
result.addType(generatedAdapter)
|
||||||
val proguardConfig = if (generateProguardRules) {
|
val proguardConfig = if (generateProguardRules) {
|
||||||
generatedAdapter.createProguardRule()
|
generatedAdapter.createProguardRule(target.instantiateAnnotations)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
return PreparedAdapter(result.build(), proguardConfig)
|
return PreparedAdapter(result.build(), proguardConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TypeSpec.createProguardRule(): ProguardConfig {
|
private fun TypeSpec.createProguardRule(instantiateAnnotations: Boolean): ProguardConfig {
|
||||||
val adapterProperties = propertySpecs
|
val adapterProperties = if (instantiateAnnotations) {
|
||||||
.asSequence()
|
// Don't need to do anything special if we instantiate them directly!
|
||||||
.filter { prop ->
|
emptySet()
|
||||||
prop.type.rawType() == JsonAdapter::class.asClassName()
|
} else {
|
||||||
}
|
propertySpecs
|
||||||
.filter { prop -> prop.annotations.isNotEmpty() }
|
.asSequence()
|
||||||
.mapTo(mutableSetOf()) { prop ->
|
.filter { prop ->
|
||||||
QualifierAdapterProperty(
|
prop.type.rawType() == JsonAdapter::class.asClassName()
|
||||||
name = prop.name,
|
}
|
||||||
qualifiers = prop.annotations.mapTo(mutableSetOf()) { it.typeName.rawType() }
|
.filter { prop -> prop.annotations.isNotEmpty() }
|
||||||
)
|
.mapTo(mutableSetOf()) { prop ->
|
||||||
}
|
QualifierAdapterProperty(
|
||||||
|
name = prop.name,
|
||||||
|
qualifiers = prop.annotations.mapTo(mutableSetOf()) { it.typeName.rawType() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val adapterConstructorParams = when (requireNotNull(primaryConstructor).parameters.size) {
|
val adapterConstructorParams = when (requireNotNull(primaryConstructor).parameters.size) {
|
||||||
1 -> listOf(CN_MOSHI.reflectionName())
|
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.AnnotationSpec
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.ClassName
|
||||||
|
import com.squareup.kotlinpoet.CodeBlock
|
||||||
import com.squareup.kotlinpoet.KModifier
|
import com.squareup.kotlinpoet.KModifier
|
||||||
import com.squareup.kotlinpoet.MemberName
|
import com.squareup.kotlinpoet.MemberName
|
||||||
import com.squareup.kotlinpoet.NameAllocator
|
import com.squareup.kotlinpoet.NameAllocator
|
||||||
@@ -29,6 +30,7 @@ import com.squareup.kotlinpoet.TypeVariableName
|
|||||||
import com.squareup.kotlinpoet.WildcardTypeName
|
import com.squareup.kotlinpoet.WildcardTypeName
|
||||||
import com.squareup.kotlinpoet.asClassName
|
import com.squareup.kotlinpoet.asClassName
|
||||||
import com.squareup.kotlinpoet.asTypeName
|
import com.squareup.kotlinpoet.asTypeName
|
||||||
|
import com.squareup.kotlinpoet.joinToCode
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import java.util.Locale
|
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. */
|
/** A JsonAdapter that can be used to encode and decode a particular field. */
|
||||||
internal data class DelegateKey(
|
internal data class DelegateKey(
|
||||||
private val type: TypeName,
|
private val type: TypeName,
|
||||||
private val jsonQualifiers: List<AnnotationSpec>
|
private val jsonQualifiers: List<AnnotationSpec>,
|
||||||
|
private val instantiateAnnotations: Boolean
|
||||||
) {
|
) {
|
||||||
val nullable get() = type.isNullable
|
val nullable get() = type.isNullable
|
||||||
|
|
||||||
@@ -60,8 +63,12 @@ internal data class DelegateKey(
|
|||||||
moshiParameter,
|
moshiParameter,
|
||||||
typeRenderer.render(type)
|
typeRenderer.render(type)
|
||||||
)
|
)
|
||||||
|
|
||||||
val (initializerString, args) = when {
|
val (initializerString, args) = when {
|
||||||
jsonQualifiers.isEmpty() -> ", %M()" to arrayOf(MemberName("kotlin.collections", "emptySet"))
|
jsonQualifiers.isEmpty() -> ", %M()" to arrayOf(MemberName("kotlin.collections", "emptySet"))
|
||||||
|
instantiateAnnotations -> {
|
||||||
|
", setOf(%L)" to arrayOf(jsonQualifiers.map { it.asInstantiationExpression() }.joinToCode())
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
", %T.getFieldJsonQualifierAnnotations(javaClass, " +
|
", %T.getFieldJsonQualifierAnnotations(javaClass, " +
|
||||||
"%S)" to arrayOf(Types::class.asTypeName(), adapterName)
|
"%S)" to arrayOf(Types::class.asTypeName(), adapterName)
|
||||||
@@ -70,12 +77,25 @@ internal data class DelegateKey(
|
|||||||
val finalArgs = arrayOf(*standardArgs, *args, propertyName)
|
val finalArgs = arrayOf(*standardArgs, *args, propertyName)
|
||||||
|
|
||||||
return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
|
return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
|
||||||
.addAnnotations(jsonQualifiers)
|
.apply {
|
||||||
|
if (!instantiateAnnotations) {
|
||||||
|
addAnnotations(jsonQualifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
.initializer("%N.adapter(%L$initializerString, %S)", *finalArgs)
|
.initializer("%N.adapter(%L$initializerString, %S)", *finalArgs)
|
||||||
.build()
|
.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,
|
* Returns a suggested variable name derived from a list of type names. This just concatenates,
|
||||||
* yielding types like MapOfStringLong.
|
* yielding types like MapOfStringLong.
|
||||||
|
@@ -36,6 +36,14 @@ internal object Options {
|
|||||||
*/
|
*/
|
||||||
const val OPTION_GENERATE_PROGUARD_RULES: String = "moshi.generateProguardRules"
|
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(
|
val POSSIBLE_GENERATED_NAMES = arrayOf(
|
||||||
ClassName("javax.annotation.processing", "Generated"),
|
ClassName("javax.annotation.processing", "Generated"),
|
||||||
ClassName("javax.annotation", "Generated")
|
ClassName("javax.annotation", "Generated")
|
||||||
|
@@ -26,7 +26,8 @@ internal data class TargetType(
|
|||||||
val properties: Map<String, TargetProperty>,
|
val properties: Map<String, TargetProperty>,
|
||||||
val typeVariables: List<TypeVariableName>,
|
val typeVariables: List<TypeVariableName>,
|
||||||
val isDataClass: Boolean,
|
val isDataClass: Boolean,
|
||||||
val visibility: KModifier
|
val visibility: KModifier,
|
||||||
|
val instantiateAnnotations: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
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.AdapterGenerator
|
||||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATED
|
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_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.Options.POSSIBLE_GENERATED_NAMES
|
||||||
import com.squareup.moshi.kotlin.codegen.api.ProguardConfig
|
import com.squareup.moshi.kotlin.codegen.api.ProguardConfig
|
||||||
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
||||||
@@ -59,6 +60,7 @@ public class JsonClassCodegenProcessor : AbstractProcessor() {
|
|||||||
private val annotation = JsonClass::class.java
|
private val annotation = JsonClass::class.java
|
||||||
private var generatedType: ClassName? = null
|
private var generatedType: ClassName? = null
|
||||||
private var generateProguardRules: Boolean = true
|
private var generateProguardRules: Boolean = true
|
||||||
|
private var instantiateAnnotations: Boolean = true
|
||||||
|
|
||||||
override fun getSupportedAnnotationTypes(): Set<String> = setOf(annotation.canonicalName)
|
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
|
generateProguardRules = processingEnv.options[OPTION_GENERATE_PROGUARD_RULES]?.toBooleanStrictOrNull() ?: true
|
||||||
|
instantiateAnnotations = processingEnv.options[OPTION_INSTANTIATE_ANNOTATIONS]?.toBooleanStrictOrNull() ?: true
|
||||||
|
|
||||||
this.types = processingEnv.typeUtils
|
this.types = processingEnv.typeUtils
|
||||||
this.elements = processingEnv.elementUtils
|
this.elements = processingEnv.elementUtils
|
||||||
@@ -135,11 +138,18 @@ public class JsonClassCodegenProcessor : AbstractProcessor() {
|
|||||||
element: TypeElement,
|
element: TypeElement,
|
||||||
cachedClassInspector: MoshiCachedClassInspector
|
cachedClassInspector: MoshiCachedClassInspector
|
||||||
): AdapterGenerator? {
|
): 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>()
|
val properties = mutableMapOf<String, PropertyGenerator>()
|
||||||
for (property in type.properties.values) {
|
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) {
|
if (generator != null) {
|
||||||
properties[property.name] = generator
|
properties[property.name] = generator
|
||||||
}
|
}
|
||||||
|
@@ -71,6 +71,7 @@ private val VISIBILITY_MODIFIERS = setOf(
|
|||||||
KModifier.PROTECTED,
|
KModifier.PROTECTED,
|
||||||
KModifier.PUBLIC
|
KModifier.PUBLIC
|
||||||
)
|
)
|
||||||
|
private val ANNOTATION_INSTANTIATION_MIN_VERSION = KotlinVersion(1, 6, 0)
|
||||||
|
|
||||||
private fun Collection<KModifier>.visibility(): KModifier {
|
private fun Collection<KModifier>.visibility(): KModifier {
|
||||||
return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC
|
return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC
|
||||||
@@ -122,7 +123,8 @@ internal fun targetType(
|
|||||||
elements: Elements,
|
elements: Elements,
|
||||||
types: Types,
|
types: Types,
|
||||||
element: TypeElement,
|
element: TypeElement,
|
||||||
cachedClassInspector: MoshiCachedClassInspector
|
cachedClassInspector: MoshiCachedClassInspector,
|
||||||
|
instantiateAnnotationsEnabled: Boolean
|
||||||
): TargetType? {
|
): TargetType? {
|
||||||
val typeMetadata = element.getAnnotation(Metadata::class.java)
|
val typeMetadata = element.getAnnotation(Metadata::class.java)
|
||||||
if (typeMetadata == null) {
|
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 kotlinApi = cachedClassInspector.toTypeSpec(kmClass)
|
||||||
val typeVariables = kotlinApi.typeVariables
|
val typeVariables = kotlinApi.typeVariables
|
||||||
val appliedType = AppliedType.get(element)
|
val appliedType = AppliedType.get(element)
|
||||||
@@ -319,7 +326,8 @@ internal fun targetType(
|
|||||||
properties = properties,
|
properties = properties,
|
||||||
typeVariables = typeVariables,
|
typeVariables = typeVariables,
|
||||||
isDataClass = KModifier.DATA in kotlinApi.modifiers,
|
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(
|
internal fun TargetProperty.generator(
|
||||||
messager: Messager,
|
messager: Messager,
|
||||||
sourceElement: TypeElement,
|
sourceElement: TypeElement,
|
||||||
elements: Elements
|
elements: Elements,
|
||||||
|
instantiateAnnotations: Boolean
|
||||||
): PropertyGenerator? {
|
): PropertyGenerator? {
|
||||||
if (isTransient) {
|
if (isTransient) {
|
||||||
if (!hasDefault) {
|
if (!hasDefault) {
|
||||||
@@ -429,7 +438,7 @@ internal fun TargetProperty.generator(
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
|
return PropertyGenerator(this, DelegateKey(type, emptyList(), instantiateAnnotations), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
@@ -452,6 +461,7 @@ internal fun TargetProperty.generator(
|
|||||||
// Check Java types since that covers both Java and Kotlin annotations.
|
// Check Java types since that covers both Java and Kotlin annotations.
|
||||||
val annotationElement = elements.getTypeElement(qualifierRawType.canonicalName)
|
val annotationElement = elements.getTypeElement(qualifierRawType.canonicalName)
|
||||||
?: continue
|
?: continue
|
||||||
|
|
||||||
annotationElement.getAnnotation(Retention::class.java)?.let {
|
annotationElement.getAnnotation(Retention::class.java)?.let {
|
||||||
if (it.value != RetentionPolicy.RUNTIME) {
|
if (it.value != RetentionPolicy.RUNTIME) {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
@@ -460,12 +470,14 @@ internal fun TargetProperty.generator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
annotationElement.getAnnotation(Target::class.java)?.let {
|
if (!instantiateAnnotations) {
|
||||||
if (ElementType.FIELD !in it.value) {
|
annotationElement.getAnnotation(Target::class.java)?.let {
|
||||||
messager.printMessage(
|
if (ElementType.FIELD !in it.value) {
|
||||||
ERROR,
|
messager.printMessage(
|
||||||
"JsonQualifier @${qualifierRawType.simpleName} must support FIELD target"
|
ERROR,
|
||||||
)
|
"JsonQualifier @${qualifierRawType.simpleName} must support FIELD target"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,7 +490,7 @@ internal fun TargetProperty.generator(
|
|||||||
|
|
||||||
return PropertyGenerator(
|
return PropertyGenerator(
|
||||||
this,
|
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.AdapterGenerator
|
||||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATED
|
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_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.Options.POSSIBLE_GENERATED_NAMES
|
||||||
import com.squareup.moshi.kotlin.codegen.api.ProguardConfig
|
import com.squareup.moshi.kotlin.codegen.api.ProguardConfig
|
||||||
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
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 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> {
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
val generatedAnnotation = generatedOption?.let {
|
val generatedAnnotation = generatedOption?.let {
|
||||||
@@ -122,11 +125,11 @@ private class JsonClassSymbolProcessor(
|
|||||||
resolver: Resolver,
|
resolver: Resolver,
|
||||||
originalType: KSDeclaration,
|
originalType: KSDeclaration,
|
||||||
): AdapterGenerator? {
|
): AdapterGenerator? {
|
||||||
val type = targetType(originalType, resolver, logger) ?: return null
|
val type = targetType(originalType, resolver, logger, instantiateAnnotations) ?: return null
|
||||||
|
|
||||||
val properties = mutableMapOf<String, PropertyGenerator>()
|
val properties = mutableMapOf<String, PropertyGenerator>()
|
||||||
for (property in type.properties.values) {
|
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) {
|
if (generator != null) {
|
||||||
properties[property.name] = generator
|
properties[property.name] = generator
|
||||||
}
|
}
|
||||||
|
@@ -69,12 +69,12 @@ private fun addValueToBlock(value: Any, resolver: Resolver, member: CodeBlock.Bu
|
|||||||
when (value) {
|
when (value) {
|
||||||
is List<*> -> {
|
is List<*> -> {
|
||||||
// Array type
|
// Array type
|
||||||
member.add("[⇥⇥")
|
member.add("arrayOf(⇥⇥")
|
||||||
value.forEachIndexed { index, innerValue ->
|
value.forEachIndexed { index, innerValue ->
|
||||||
if (index > 0) member.add(", ")
|
if (index > 0) member.add(", ")
|
||||||
addValueToBlock(innerValue!!, resolver, member)
|
addValueToBlock(innerValue!!, resolver, member)
|
||||||
}
|
}
|
||||||
member.add("⇤⇤]")
|
member.add("⇤⇤)")
|
||||||
}
|
}
|
||||||
is KSType -> {
|
is KSType -> {
|
||||||
val unwrapped = value.unwrapTypeAlias()
|
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.TargetProperty
|
||||||
import com.squareup.moshi.kotlin.codegen.api.rawType
|
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.isTransient get() = propertySpec.annotations.any { it.typeName == Transient::class.asClassName() }
|
||||||
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
|
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
|
||||||
private val TargetProperty.isVisible: Boolean
|
private val TargetProperty.isVisible: Boolean
|
||||||
@@ -55,7 +44,8 @@ private val TargetProperty.isVisible: Boolean
|
|||||||
internal fun TargetProperty.generator(
|
internal fun TargetProperty.generator(
|
||||||
logger: KSPLogger,
|
logger: KSPLogger,
|
||||||
resolver: Resolver,
|
resolver: Resolver,
|
||||||
originalType: KSDeclaration
|
originalType: KSDeclaration,
|
||||||
|
instantiateAnnotations: Boolean
|
||||||
): PropertyGenerator? {
|
): PropertyGenerator? {
|
||||||
if (isTransient) {
|
if (isTransient) {
|
||||||
if (!hasDefault) {
|
if (!hasDefault) {
|
||||||
@@ -65,7 +55,7 @@ internal fun TargetProperty.generator(
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
|
return PropertyGenerator(this, DelegateKey(type, emptyList(), instantiateAnnotations), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
@@ -93,11 +83,13 @@ internal fun TargetProperty.generator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
annotationElement.findAnnotationWithType<Target>()?.let {
|
if (!instantiateAnnotations) {
|
||||||
if (AnnotationTarget.FIELD !in it.allowedTargets) {
|
annotationElement.findAnnotationWithType<Target>()?.let {
|
||||||
logger.error(
|
if (AnnotationTarget.FIELD !in it.allowedTargets) {
|
||||||
"JsonQualifier @${qualifierRawType.simpleName} must support FIELD target"
|
logger.error(
|
||||||
)
|
"JsonQualifier @${qualifierRawType.simpleName} must support FIELD target"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +103,7 @@ internal fun TargetProperty.generator(
|
|||||||
|
|
||||||
return PropertyGenerator(
|
return PropertyGenerator(
|
||||||
this,
|
this,
|
||||||
DelegateKey(type, jsonQualifierSpecs)
|
DelegateKey(type, jsonQualifierSpecs, instantiateAnnotations)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -58,6 +58,7 @@ internal fun targetType(
|
|||||||
type: KSDeclaration,
|
type: KSDeclaration,
|
||||||
resolver: Resolver,
|
resolver: Resolver,
|
||||||
logger: KSPLogger,
|
logger: KSPLogger,
|
||||||
|
instantiateAnnotations: Boolean
|
||||||
): TargetType? {
|
): TargetType? {
|
||||||
if (type !is KSClassDeclaration) {
|
if (type !is KSClassDeclaration) {
|
||||||
logger.error("@JsonClass can't be applied to ${type.qualifiedName?.asString()}: must be a Kotlin class", type)
|
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,
|
properties = properties,
|
||||||
typeVariables = typeVariables,
|
typeVariables = typeVariables,
|
||||||
isDataClass = Modifier.DATA in type.modifiers,
|
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.google.common.truth.Truth.assertThat
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.JsonReader
|
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_GENERATED
|
||||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATE_PROGUARD_RULES
|
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.KotlinCompilation
|
||||||
import com.tschuchort.compiletesting.SourceFile
|
import com.tschuchort.compiletesting.SourceFile
|
||||||
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
|
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
|
||||||
@@ -28,6 +28,8 @@ import org.junit.Ignore
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KClassifier
|
import kotlin.reflect.KClassifier
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
@@ -38,7 +40,24 @@ import kotlin.reflect.full.createType
|
|||||||
import kotlin.reflect.full.declaredMemberProperties
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
|
||||||
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
|
/** 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()
|
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -455,8 +474,13 @@ class JsonClassCodegenProcessorTest {
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
assertThat(result.messages).contains("JsonQualifier @UpperCase must support FIELD target")
|
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
|
@Test
|
||||||
@@ -665,19 +689,34 @@ class JsonClassCodegenProcessorTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
"moshi-testPackage.UsingQualifiers" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.UsingQualifiers" -> {
|
||||||
"""
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
-if class testPackage.UsingQualifiers
|
assertThat(generatedFile.readText()).contains(
|
||||||
-keepnames class testPackage.UsingQualifiers
|
"""
|
||||||
-if class testPackage.UsingQualifiers
|
-if class testPackage.UsingQualifiers
|
||||||
-keep class testPackage.UsingQualifiersJsonAdapter {
|
-keepnames class testPackage.UsingQualifiers
|
||||||
public <init>(com.squareup.moshi.Moshi);
|
-if class testPackage.UsingQualifiers
|
||||||
private com.squareup.moshi.JsonAdapter stringAtMyQualifierAdapter;
|
-keep class testPackage.UsingQualifiersJsonAdapter {
|
||||||
|
public <init>(com.squareup.moshi.Moshi);
|
||||||
|
private com.squareup.moshi.JsonAdapter stringAtMyQualifierAdapter;
|
||||||
|
}
|
||||||
|
-if class testPackage.UsingQualifiers
|
||||||
|
-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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
-if class testPackage.UsingQualifiers
|
}
|
||||||
-keep @interface testPackage.MyQualifier
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
"moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
|
||||||
"""
|
"""
|
||||||
-if class testPackage.MixedTypes
|
-if class testPackage.MixedTypes
|
||||||
@@ -704,25 +743,46 @@ class JsonClassCodegenProcessorTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
"moshi-testPackage.Complex" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.Complex" -> {
|
||||||
"""
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
-if class testPackage.Complex
|
assertThat(generatedFile.readText()).contains(
|
||||||
-keepnames class testPackage.Complex
|
"""
|
||||||
-if class testPackage.Complex
|
-if class testPackage.Complex
|
||||||
-keep class testPackage.ComplexJsonAdapter {
|
-keepnames class testPackage.Complex
|
||||||
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
|
-if class testPackage.Complex
|
||||||
private com.squareup.moshi.JsonAdapter mutableListOfStringAtMyQualifierAdapter;
|
-keep class testPackage.ComplexJsonAdapter {
|
||||||
|
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
|
||||||
|
private com.squareup.moshi.JsonAdapter mutableListOfStringAtMyQualifierAdapter;
|
||||||
|
}
|
||||||
|
-if class testPackage.Complex
|
||||||
|
-keep @interface testPackage.MyQualifier
|
||||||
|
-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()
|
||||||
|
)
|
||||||
|
} 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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
-if class testPackage.Complex
|
}
|
||||||
-keep @interface testPackage.MyQualifier
|
|
||||||
-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(
|
"moshi-testPackage.MultipleMasks" -> assertThat(generatedFile.readText()).contains(
|
||||||
"""
|
"""
|
||||||
-if class testPackage.MultipleMasks
|
-if class testPackage.MultipleMasks
|
||||||
@@ -739,19 +799,34 @@ class JsonClassCodegenProcessorTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
"moshi-testPackage.NestedType.NestedSimple" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.NestedType.NestedSimple" -> {
|
||||||
"""
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
-if class testPackage.NestedType${'$'}NestedSimple
|
assertThat(generatedFile.readText()).contains(
|
||||||
-keepnames class testPackage.NestedType${'$'}NestedSimple
|
"""
|
||||||
-if class testPackage.NestedType${'$'}NestedSimple
|
-if class testPackage.NestedType${'$'}NestedSimple
|
||||||
-keep class testPackage.NestedType_NestedSimpleJsonAdapter {
|
-keepnames class testPackage.NestedType${'$'}NestedSimple
|
||||||
public <init>(com.squareup.moshi.Moshi);
|
-if class testPackage.NestedType${'$'}NestedSimple
|
||||||
private com.squareup.moshi.JsonAdapter stringAtNestedQualifierAdapter;
|
-keep class testPackage.NestedType_NestedSimpleJsonAdapter {
|
||||||
|
public <init>(com.squareup.moshi.Moshi);
|
||||||
|
private com.squareup.moshi.JsonAdapter stringAtNestedQualifierAdapter;
|
||||||
|
}
|
||||||
|
-if class testPackage.NestedType${'$'}NestedSimple
|
||||||
|
-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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
-if class testPackage.NestedType${'$'}NestedSimple
|
}
|
||||||
-keep @interface testPackage.NestedType${'$'}NestedQualifier
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
else -> error("Unexpected proguard file! ${generatedFile.name}")
|
else -> error("Unexpected proguard file! ${generatedFile.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -765,6 +840,8 @@ class JsonClassCodegenProcessorTest {
|
|||||||
inheritClassPath = true
|
inheritClassPath = true
|
||||||
sources = sourceFiles.asList()
|
sources = sourceFiles.asList()
|
||||||
verbose = false
|
verbose = false
|
||||||
|
kotlincArguments = listOf("-language-version", languageVersion)
|
||||||
|
kaptArgs[OPTION_INSTANTIATE_ANNOTATIONS] = "$instantiateAnnotations"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
package com.squareup.moshi.kotlin.codegen.ksp
|
package com.squareup.moshi.kotlin.codegen.ksp
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
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_GENERATED
|
||||||
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATE_PROGUARD_RULES
|
import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATE_PROGUARD_RULES
|
||||||
import com.tschuchort.compiletesting.KotlinCompilation
|
import com.tschuchort.compiletesting.KotlinCompilation
|
||||||
@@ -30,15 +31,41 @@ import org.junit.Ignore
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
import kotlin.reflect.KClassifier
|
import org.junit.runner.RunWith
|
||||||
import kotlin.reflect.KType
|
import org.junit.runners.Parameterized
|
||||||
import kotlin.reflect.KTypeProjection
|
|
||||||
import kotlin.reflect.KVariance
|
|
||||||
import kotlin.reflect.KVariance.INVARIANT
|
|
||||||
import kotlin.reflect.full.createType
|
|
||||||
|
|
||||||
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
|
/** 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()
|
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
|
||||||
|
|
||||||
@@ -490,8 +517,13 @@ class JsonClassSymbolProcessorTest {
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
assertThat(result.messages).contains("JsonQualifier @UpperCase must support FIELD target")
|
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
|
@Test
|
||||||
@@ -704,19 +736,34 @@ class JsonClassSymbolProcessorTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
"moshi-testPackage.UsingQualifiers" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.UsingQualifiers" -> {
|
||||||
"""
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
-if class testPackage.UsingQualifiers
|
assertThat(generatedFile.readText()).contains(
|
||||||
-keepnames class testPackage.UsingQualifiers
|
"""
|
||||||
-if class testPackage.UsingQualifiers
|
-if class testPackage.UsingQualifiers
|
||||||
-keep class testPackage.UsingQualifiersJsonAdapter {
|
-keepnames class testPackage.UsingQualifiers
|
||||||
public <init>(com.squareup.moshi.Moshi);
|
-if class testPackage.UsingQualifiers
|
||||||
private com.squareup.moshi.JsonAdapter stringAtMyQualifierAdapter;
|
-keep class testPackage.UsingQualifiersJsonAdapter {
|
||||||
|
public <init>(com.squareup.moshi.Moshi);
|
||||||
|
private com.squareup.moshi.JsonAdapter stringAtMyQualifierAdapter;
|
||||||
|
}
|
||||||
|
-if class testPackage.UsingQualifiers
|
||||||
|
-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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
-if class testPackage.UsingQualifiers
|
}
|
||||||
-keep @interface testPackage.MyQualifier
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
"moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.MixedTypes" -> assertThat(generatedFile.readText()).contains(
|
||||||
"""
|
"""
|
||||||
-if class testPackage.MixedTypes
|
-if class testPackage.MixedTypes
|
||||||
@@ -743,25 +790,46 @@ class JsonClassSymbolProcessorTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
"moshi-testPackage.Complex" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.Complex" -> {
|
||||||
"""
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
-if class testPackage.Complex
|
assertThat(generatedFile.readText()).contains(
|
||||||
-keepnames class testPackage.Complex
|
"""
|
||||||
-if class testPackage.Complex
|
-if class testPackage.Complex
|
||||||
-keep class testPackage.ComplexJsonAdapter {
|
-keepnames class testPackage.Complex
|
||||||
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
|
-if class testPackage.Complex
|
||||||
private com.squareup.moshi.JsonAdapter mutableListOfStringAtMyQualifierAdapter;
|
-keep class testPackage.ComplexJsonAdapter {
|
||||||
|
public <init>(com.squareup.moshi.Moshi,java.lang.reflect.Type[]);
|
||||||
|
private com.squareup.moshi.JsonAdapter mutableListOfStringAtMyQualifierAdapter;
|
||||||
|
}
|
||||||
|
-if class testPackage.Complex
|
||||||
|
-keep @interface testPackage.MyQualifier
|
||||||
|
-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()
|
||||||
|
)
|
||||||
|
} 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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
-if class testPackage.Complex
|
}
|
||||||
-keep @interface testPackage.MyQualifier
|
|
||||||
-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(
|
"moshi-testPackage.MultipleMasks" -> assertThat(generatedFile.readText()).contains(
|
||||||
"""
|
"""
|
||||||
-if class testPackage.MultipleMasks
|
-if class testPackage.MultipleMasks
|
||||||
@@ -778,19 +846,34 @@ class JsonClassSymbolProcessorTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
"moshi-testPackage.NestedType.NestedSimple" -> assertThat(generatedFile.readText()).contains(
|
"moshi-testPackage.NestedType.NestedSimple" -> {
|
||||||
"""
|
if (languageVersion == "1.5" || !instantiateAnnotations) {
|
||||||
-if class testPackage.NestedType${'$'}NestedSimple
|
assertThat(generatedFile.readText()).contains(
|
||||||
-keepnames class testPackage.NestedType${'$'}NestedSimple
|
"""
|
||||||
-if class testPackage.NestedType${'$'}NestedSimple
|
-if class testPackage.NestedType${'$'}NestedSimple
|
||||||
-keep class testPackage.NestedType_NestedSimpleJsonAdapter {
|
-keepnames class testPackage.NestedType${'$'}NestedSimple
|
||||||
public <init>(com.squareup.moshi.Moshi);
|
-if class testPackage.NestedType${'$'}NestedSimple
|
||||||
private com.squareup.moshi.JsonAdapter stringAtNestedQualifierAdapter;
|
-keep class testPackage.NestedType_NestedSimpleJsonAdapter {
|
||||||
|
public <init>(com.squareup.moshi.Moshi);
|
||||||
|
private com.squareup.moshi.JsonAdapter stringAtNestedQualifierAdapter;
|
||||||
|
}
|
||||||
|
-if class testPackage.NestedType${'$'}NestedSimple
|
||||||
|
-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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
-if class testPackage.NestedType${'$'}NestedSimple
|
}
|
||||||
-keep @interface testPackage.NestedType${'$'}NestedQualifier
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
else -> error("Unexpected proguard file! ${generatedFile.name}")
|
else -> error("Unexpected proguard file! ${generatedFile.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -805,20 +888,12 @@ class JsonClassSymbolProcessorTest {
|
|||||||
sources = sourceFiles.asList()
|
sources = sourceFiles.asList()
|
||||||
verbose = false
|
verbose = false
|
||||||
kspIncremental = true // The default now
|
kspIncremental = true // The default now
|
||||||
|
kotlincArguments = listOf("-language-version", languageVersion)
|
||||||
|
kspArgs[Options.OPTION_INSTANTIATE_ANNOTATIONS] = "$instantiateAnnotations"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compile(vararg sourceFiles: SourceFile): KotlinCompilation.Result {
|
private fun compile(vararg sourceFiles: SourceFile): KotlinCompilation.Result {
|
||||||
return prepareCompilation(*sourceFiles).compile()
|
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 {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("kapt") apply false
|
kotlin("kapt") apply false
|
||||||
alias(libs.plugins.ksp) apply false
|
id("com.google.devtools.ksp") apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
val useKsp = hasProperty("useKsp")
|
val useKsp = hasProperty("useKsp")
|
||||||
@@ -34,11 +34,14 @@ tasks.withType<Test>().configureEach {
|
|||||||
jvmArgs("--add-opens=java.base/java.io=ALL-UNNAMED")
|
jvmArgs("--add-opens=java.base/java.io=ALL-UNNAMED")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useWError = findProperty("kotlinLanguageVersion")?.toString()
|
||||||
|
?.startsWith("1.5")
|
||||||
|
?: false
|
||||||
tasks.withType<KotlinCompile>().configureEach {
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
allWarningsAsErrors = useWError
|
||||||
@Suppress("SuspiciousCollectionReassignment")
|
@Suppress("SuspiciousCollectionReassignment")
|
||||||
freeCompilerArgs += listOf(
|
freeCompilerArgs += listOf(
|
||||||
"-Werror",
|
|
||||||
"-Xopt-in=kotlin.ExperimentalStdlibApi"
|
"-Xopt-in=kotlin.ExperimentalStdlibApi"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user