mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Generate @Generated
annotation onto adapters when possible (#466)
* Generate `@Generated` annotation onto adapters when possible Part of #461 This leverages AutoCommon's `GeneratedAnnotations#generatedAnnotation` API to generate a `@Generated` annotation where possible. This keeps with conventions in other code gen tools, and also allows for more fine grained proguard rules for keeping generated adapter names, like so: ```proguard -keepnames @com.squareup.moshi.<wherever this ends up>.MoshiSerializable class * # Java < 9 -keepnames @javax.annotation.Generated class **JsonAdapter # Java 9+ -keepnames @javax.annotation.processing.Generated class **JsonAdapter ``` Generated annotation looks like this: ```kotlin @Generated( value = ["com.squareup.moshi.MoshiKotlinCodeGenProcessor"], comments = "https://github.com/square/moshi" ) ``` This doooooes also replace `elements` in `AdapterGenerator` with a `ProcessingEnv`, but I figured that was less evil than polluting it with both `elements` and plumbing down an `env` separately simultaneously. This does also hit a weird ambiguity case due to `KotlinMetadataUtils`' repeat declaration, so a good reason for removing that in the future. Figured it best to punt on a better final place for this to another time. * Remove names and brackets * Add moshi.generated option * Switch back to element property rather than processingEnv * Fold the kotlin-codegen-runtime into Moshi itself. Rename @MoshiSerializable to @JsonClass. Like @Json, I'm anticipating a future where there are other interesting properties on this annotation. Perhaps a future feature where Moshi is strict and only adapts types that have a '@JsonClass' annotation. Also rename MoshiKotlinCodeGenProcessor to JsonClassCodeGenProcessor. We may later support other ways of generating code here; perhaps for regular Java types. * Generate `@Generated` annotation onto adapters when possible Part of #461 This leverages AutoCommon's `GeneratedAnnotations#generatedAnnotation` API to generate a `@Generated` annotation where possible. This keeps with conventions in other code gen tools, and also allows for more fine grained proguard rules for keeping generated adapter names, like so: ```proguard -keepnames @com.squareup.moshi.<wherever this ends up>.MoshiSerializable class * # Java < 9 -keepnames @javax.annotation.Generated class **JsonAdapter # Java 9+ -keepnames @javax.annotation.processing.Generated class **JsonAdapter ``` Generated annotation looks like this: ```kotlin @Generated( value = ["com.squareup.moshi.MoshiKotlinCodeGenProcessor"], comments = "https://github.com/square/moshi" ) ``` This doooooes also replace `elements` in `AdapterGenerator` with a `ProcessingEnv`, but I figured that was less evil than polluting it with both `elements` and plumbing down an `env` separately simultaneously. This does also hit a weird ambiguity case due to `KotlinMetadataUtils`' repeat declaration, so a good reason for removing that in the future. Figured it best to punt on a better final place for this to another time. * Fix rebase conflicts and sync with remote
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
package com.squareup.moshi
|
package com.squareup.moshi
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.ARRAY
|
import com.squareup.kotlinpoet.ARRAY
|
||||||
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.ClassName
|
||||||
import com.squareup.kotlinpoet.FileSpec
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
@@ -31,6 +32,7 @@ import com.squareup.kotlinpoet.asTypeName
|
|||||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import javax.lang.model.element.Element
|
import javax.lang.model.element.Element
|
||||||
|
import javax.lang.model.element.TypeElement
|
||||||
import javax.lang.model.util.Elements
|
import javax.lang.model.util.Elements
|
||||||
|
|
||||||
/** Generates a JSON adapter for a target type. */
|
/** Generates a JSON adapter for a target type. */
|
||||||
@@ -81,7 +83,7 @@ internal class AdapterGenerator(
|
|||||||
|
|
||||||
val delegateAdapters = propertyList.distinctBy { it.delegateKey() }
|
val delegateAdapters = propertyList.distinctBy { it.delegateKey() }
|
||||||
|
|
||||||
fun generateFile(): FileSpec {
|
fun generateFile(generatedOption: TypeElement?): FileSpec {
|
||||||
for (property in delegateAdapters) {
|
for (property in delegateAdapters) {
|
||||||
property.reserveDelegateNames(nameAllocator)
|
property.reserveDelegateNames(nameAllocator)
|
||||||
}
|
}
|
||||||
@@ -93,12 +95,20 @@ internal class AdapterGenerator(
|
|||||||
if (hasCompanionObject) {
|
if (hasCompanionObject) {
|
||||||
result.addFunction(generateJsonAdapterFun())
|
result.addFunction(generateJsonAdapterFun())
|
||||||
}
|
}
|
||||||
result.addType(generateType())
|
result.addType(generateType(generatedOption))
|
||||||
return result.build()
|
return result.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateType(): TypeSpec {
|
private fun generateType(generatedOption: TypeElement?): TypeSpec {
|
||||||
val result = TypeSpec.classBuilder(adapterName)
|
val result = TypeSpec.classBuilder(adapterName)
|
||||||
|
|
||||||
|
generatedOption?.let {
|
||||||
|
result.addAnnotation(AnnotationSpec.builder(it.asClassName())
|
||||||
|
.addMember("%S", JsonClassCodeGenProcessor::class.java.canonicalName)
|
||||||
|
.addMember("%S", "https://github.com/square/moshi")
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
result.superclass(jsonAdapterTypeName)
|
result.superclass(jsonAdapterTypeName)
|
||||||
|
|
||||||
genericTypeNames?.let {
|
genericTypeNames?.let {
|
||||||
|
@@ -36,6 +36,7 @@ import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
|
|||||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||||
import org.jetbrains.kotlin.serialization.ProtoBuf.ValueParameter
|
import org.jetbrains.kotlin.serialization.ProtoBuf.ValueParameter
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import javax.annotation.processing.ProcessingEnvironment
|
||||||
import javax.annotation.processing.Processor
|
import javax.annotation.processing.Processor
|
||||||
import javax.annotation.processing.RoundEnvironment
|
import javax.annotation.processing.RoundEnvironment
|
||||||
import javax.lang.model.SourceVersion
|
import javax.lang.model.SourceVersion
|
||||||
@@ -61,18 +62,49 @@ import javax.tools.Diagnostic.Kind.ERROR
|
|||||||
@AutoService(Processor::class)
|
@AutoService(Processor::class)
|
||||||
class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils {
|
class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* This annotation processing argument can be specified to have a `@Generated` annotation
|
||||||
|
* included in the generated code. It is not encouraged unless you need it for static analysis
|
||||||
|
* reasons and not enabled by default.
|
||||||
|
*
|
||||||
|
* Note that this can only be one of the following values:
|
||||||
|
* * `"javax.annotation.processing.Generated"` (JRE 9+)
|
||||||
|
* * `"javax.annotation.Generated"` (JRE <9)
|
||||||
|
*/
|
||||||
|
const val OPTION_GENERATED = "moshi.generated"
|
||||||
|
private val POSSIBLE_GENERATED_NAMES = setOf(
|
||||||
|
"javax.annotation.processing.Generated",
|
||||||
|
"javax.annotation.Generated"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val annotation = JsonClass::class.java
|
private val annotation = JsonClass::class.java
|
||||||
|
private var generatedType: TypeElement? = null
|
||||||
|
|
||||||
override fun getSupportedAnnotationTypes() = setOf(annotation.canonicalName)
|
override fun getSupportedAnnotationTypes() = setOf(annotation.canonicalName)
|
||||||
|
|
||||||
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
|
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
|
||||||
|
|
||||||
|
override fun getSupportedOptions() = setOf(OPTION_GENERATED)
|
||||||
|
|
||||||
|
override fun init(processingEnv: ProcessingEnvironment) {
|
||||||
|
super.init(processingEnv)
|
||||||
|
generatedType = processingEnv.options[OPTION_GENERATED]?.let {
|
||||||
|
if (it !in POSSIBLE_GENERATED_NAMES) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Invalid option value for $OPTION_GENERATED. Found $it, allowable values are $POSSIBLE_GENERATED_NAMES.")
|
||||||
|
}
|
||||||
|
processingEnv.elementUtils.getTypeElement(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||||
for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
|
for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
|
||||||
val jsonClass = type.getAnnotation(annotation)
|
val jsonClass = type.getAnnotation(annotation)
|
||||||
if (jsonClass.generateAdapter) {
|
if (jsonClass.generateAdapter) {
|
||||||
val adapterGenerator = processElement(type) ?: continue
|
val adapterGenerator = processElement(type) ?: continue
|
||||||
adapterGenerator.generateAndWrite()
|
adapterGenerator.generateAndWrite(generatedType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +274,8 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
element)
|
element)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AdapterGenerator.generateAndWrite() {
|
private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) {
|
||||||
val fileSpec = generateFile()
|
val fileSpec = generateFile(generatedOption)
|
||||||
val adapterName = fileSpec.members.filterIsInstance<TypeSpec>().first().name!!
|
val adapterName = fileSpec.members.filterIsInstance<TypeSpec>().first().name!!
|
||||||
val outputDir = generatedDir ?: mavenGeneratedDir(adapterName)
|
val outputDir = generatedDir ?: mavenGeneratedDir(adapterName)
|
||||||
fileSpec.writeTo(outputDir)
|
fileSpec.writeTo(outputDir)
|
||||||
|
Reference in New Issue
Block a user