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:
Zac Sweers
2018-04-03 00:27:57 -07:00
committed by Jesse Wilson
parent e0d84e1fee
commit 5c45d1e0d9
2 changed files with 49 additions and 7 deletions

View File

@@ -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 {
@@ -307,4 +317,4 @@ internal class AdapterGenerator(
return result.build() return result.build()
} }
} }

View File

@@ -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)