mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09: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
|
||||
|
||||
import com.squareup.kotlinpoet.ARRAY
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
@@ -31,6 +32,7 @@ import com.squareup.kotlinpoet.asTypeName
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import java.lang.reflect.Type
|
||||
import javax.lang.model.element.Element
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.util.Elements
|
||||
|
||||
/** Generates a JSON adapter for a target type. */
|
||||
@@ -81,7 +83,7 @@ internal class AdapterGenerator(
|
||||
|
||||
val delegateAdapters = propertyList.distinctBy { it.delegateKey() }
|
||||
|
||||
fun generateFile(): FileSpec {
|
||||
fun generateFile(generatedOption: TypeElement?): FileSpec {
|
||||
for (property in delegateAdapters) {
|
||||
property.reserveDelegateNames(nameAllocator)
|
||||
}
|
||||
@@ -93,12 +95,20 @@ internal class AdapterGenerator(
|
||||
if (hasCompanionObject) {
|
||||
result.addFunction(generateJsonAdapterFun())
|
||||
}
|
||||
result.addType(generateType())
|
||||
result.addType(generateType(generatedOption))
|
||||
return result.build()
|
||||
}
|
||||
|
||||
private fun generateType(): TypeSpec {
|
||||
private fun generateType(generatedOption: TypeElement?): TypeSpec {
|
||||
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)
|
||||
|
||||
genericTypeNames?.let {
|
||||
@@ -307,4 +317,4 @@ internal class AdapterGenerator(
|
||||
|
||||
return result.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf.ValueParameter
|
||||
import java.io.File
|
||||
import javax.annotation.processing.ProcessingEnvironment
|
||||
import javax.annotation.processing.Processor
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import javax.lang.model.SourceVersion
|
||||
@@ -61,18 +62,49 @@ import javax.tools.Diagnostic.Kind.ERROR
|
||||
@AutoService(Processor::class)
|
||||
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 var generatedType: TypeElement? = null
|
||||
|
||||
override fun getSupportedAnnotationTypes() = setOf(annotation.canonicalName)
|
||||
|
||||
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 {
|
||||
for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
|
||||
val jsonClass = type.getAnnotation(annotation)
|
||||
if (jsonClass.generateAdapter) {
|
||||
val adapterGenerator = processElement(type) ?: continue
|
||||
adapterGenerator.generateAndWrite()
|
||||
adapterGenerator.generateAndWrite(generatedType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,8 +274,8 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
||||
element)
|
||||
}
|
||||
|
||||
private fun AdapterGenerator.generateAndWrite() {
|
||||
val fileSpec = generateFile()
|
||||
private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) {
|
||||
val fileSpec = generateFile(generatedOption)
|
||||
val adapterName = fileSpec.members.filterIsInstance<TypeSpec>().first().name!!
|
||||
val outputDir = generatedDir ?: mavenGeneratedDir(adapterName)
|
||||
fileSpec.writeTo(outputDir)
|
||||
|
Reference in New Issue
Block a user