1717
1818package com.slack.auto.value.kotlin
1919
20- import com.google.auto.service.AutoService
20+ import com.google.auto.common.MoreElements
21+ import com.google.auto.common.MoreElements.isAnnotationPresent
22+ import com.google.auto.value.AutoValue
2123import com.google.auto.value.extension.AutoValueExtension
2224import com.google.auto.value.extension.AutoValueExtension.BuilderContext
2325import com.slack.auto.value.kotlin.AvkBuilder.BuilderProperty
@@ -29,13 +31,17 @@ import com.squareup.kotlinpoet.FunSpec
2931import com.squareup.kotlinpoet.KModifier
3032import com.squareup.kotlinpoet.PropertySpec
3133import com.squareup.kotlinpoet.TypeName
34+ import com.squareup.kotlinpoet.TypeSpec
3235import com.squareup.kotlinpoet.asClassName
3336import com.squareup.kotlinpoet.asTypeVariableName
3437import com.squareup.kotlinpoet.joinToCode
3538import com.squareup.moshi.Json
3639import java.util.Locale
40+ import java.util.concurrent.ConcurrentHashMap
41+ import javax.annotation.processing.Messager
3742import javax.annotation.processing.ProcessingEnvironment
3843import javax.lang.model.element.Element
44+ import javax.lang.model.element.ElementKind
3945import javax.lang.model.element.ExecutableElement
4046import javax.lang.model.element.Modifier
4147import javax.lang.model.element.NestingKind
@@ -44,8 +50,7 @@ import javax.lang.model.util.Elements
4450import javax.lang.model.util.Types
4551import javax.tools.Diagnostic
4652
47- @AutoService(AutoValueExtension ::class )
48- public class AutoValueKotlinExtension : AutoValueExtension () {
53+ public class AutoValueKotlinExtension (private val realMessager : Messager ) : AutoValueExtension() {
4954
5055 public companion object {
5156 // Options
@@ -54,6 +59,8 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
5459 public const val OPT_IGNORE_NESTED : String = " avkIgnoreNested"
5560 }
5661
62+ internal val collectedKclassees = ConcurrentHashMap <ClassName , KotlinClass >()
63+ internal val collectedEnums = ConcurrentHashMap <ClassName , TypeSpec >()
5764 private lateinit var elements: Elements
5865 private lateinit var types: Types
5966
@@ -74,14 +81,7 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
7481 }
7582
7683 private fun FunSpec.Builder.withDocsFrom (e : Element ): FunSpec .Builder {
77- return withDocsFrom(e) { parseDocs() }
78- }
79-
80- @Suppress(" ReturnCount" )
81- private fun Element.parseDocs (): String? {
82- val doc = elements.getDocComment(this )?.trim() ? : return null
83- if (doc.isBlank()) return null
84- return cleanUpDoc(doc)
84+ return withDocsFrom(e) { parseDocs(elements) }
8585 }
8686
8787 @Suppress(" DEPRECATION" , " LongMethod" , " ComplexMethod" , " NestedBlockDepth" , " ReturnCount" )
@@ -91,33 +91,36 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
9191 classToExtend : String ,
9292 isFinal : Boolean
9393 ): String? {
94- val targetClasses = context.processingEnvironment().options[OPT_TARGETS ]
95- ?.splitToSequence(" :" )
96- ?.toSet()
97- ? : emptySet()
94+ val options = Options (context.processingEnvironment().options)
9895
9996 val ignoreNested =
10097 context.processingEnvironment().options[OPT_IGNORE_NESTED ]?.toBoolean() ? : false
10198
102- if (targetClasses. isNotEmpty() && context.autoValueClass().simpleName.toString() !in targetClasses ) {
99+ if (options.targets. isNotEmpty() && context.autoValueClass().simpleName.toString() !in options.targets ) {
103100 return null
104101 }
105102
106103 val avClass = context.autoValueClass()
107104
108- if (avClass.nestingKind != NestingKind .TOP_LEVEL ) {
109- val diagnosticKind = if (ignoreNested) {
110- Diagnostic .Kind .WARNING
111- } else {
112- Diagnostic .Kind .ERROR
105+ val isTopLevel = avClass.nestingKind == NestingKind .TOP_LEVEL
106+ if (! isTopLevel) {
107+ val isParentAv = isAnnotationPresent(
108+ MoreElements .asType(avClass.enclosingElement),
109+ AutoValue ::class .java
110+ )
111+ if (! isParentAv) {
112+ val diagnosticKind = if (ignoreNested) {
113+ Diagnostic .Kind .WARNING
114+ } else {
115+ Diagnostic .Kind .ERROR
116+ }
117+ realMessager
118+ .printMessage(
119+ diagnosticKind,
120+ " Cannot convert nested classes to Kotlin safely. Please move this to top-level first." ,
121+ avClass
122+ )
113123 }
114- context.processingEnvironment().messager
115- .printMessage(
116- diagnosticKind,
117- " Cannot convert nested classes to Kotlin safely. Please move this to top-level first." ,
118- avClass
119- )
120- return null
121124 }
122125
123126 // Check for non-builder nested classes, which cannot be converted with this
@@ -128,19 +131,32 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
128131 .orElse(false )
129132 }
130133
131- if (nonBuilderNestedTypes.isNotEmpty()) {
132- nonBuilderNestedTypes.forEach {
133- context.processingEnvironment().messager
134+ val (enums, nonEnums) = nonBuilderNestedTypes.partition { it.kind == ElementKind .ENUM }
135+
136+ val (nestedAvClasses, remainingTypes) = nonEnums.partition { isAnnotationPresent(it, AutoValue ::class .java) }
137+
138+ if (remainingTypes.isNotEmpty()) {
139+ remainingTypes.forEach {
140+ realMessager
134141 .printMessage(
135142 Diagnostic .Kind .ERROR ,
136- " Cannot convert nested classes to Kotlin safely. Please move this to top-level first." ,
143+ " Cannot convert non-autovalue nested classes to Kotlin safely. Please move this to top-level first." ,
137144 it
138145 )
139146 }
140147 return null
141148 }
142149
143- val classDoc = avClass.parseDocs()
150+ for (enumType in enums) {
151+ val (cn, spec) = EnumConversion .convert(
152+ elements,
153+ realMessager,
154+ enumType
155+ ) ? : continue
156+ collectedEnums[cn] = spec
157+ }
158+
159+ val classDoc = avClass.parseDocs(elements)
144160
145161 var redactedClassName: ClassName ? = null
146162
@@ -189,7 +205,7 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
189205 isOverride = isAnOverride,
190206 isRedacted = isRedacted,
191207 visibility = if (Modifier .PUBLIC in method.modifiers) KModifier .PUBLIC else KModifier .INTERNAL ,
192- doc = method.parseDocs()
208+ doc = method.parseDocs(elements )
193209 )
194210 }
195211
@@ -229,14 +245,13 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
229245 // Note we don't use context.propertyTypes() here because it doesn't contain nullability
230246 // info, which we did capture
231247 val propertyTypes = properties.mapValues { it.value.type }
232- avkBuilder = AvkBuilder .from(builder, propertyTypes) { parseDocs() }
248+ avkBuilder = AvkBuilder .from(builder, propertyTypes) { parseDocs(elements ) }
233249
234250 builderFactories + = builder.builderMethods()
235251 builderFactorySpecs + = builder.builderMethods()
236252 .map {
237253 FunSpec .copyOf(it)
238254 .withDocsFrom(it)
239- .addModifiers(avkBuilder.visibility)
240255 .addStatement(" TODO(%S)" , " Replace this with the implementation from the source class" )
241256 .build()
242257 }
@@ -387,22 +402,19 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
387402 initializer(" TODO()" )
388403 }
389404
390- field.parseDocs()?.let { addKdoc(it) }
405+ field.parseDocs(elements )?.let { addKdoc(it) }
391406 }
392407 .build()
393408 }
394409
395410 val superclass = avClass.superclass.asSafeTypeName()
396411 .takeUnless { it == ClassName (" java.lang" , " Object" ) }
397412
398- val srcDir =
399- context.processingEnvironment().options[OPT_SRC ] ? : error(" Missing src dir option" )
400-
401- KotlinClass (
413+ val kClass = KotlinClass (
402414 packageName = context.packageName(),
403415 doc = classDoc,
404416 name = avClass.simpleName.toString(),
405- visibility = if ( Modifier . PUBLIC in avClass.modifiers) KModifier . PUBLIC else KModifier . INTERNAL ,
417+ visibility = avClass.visibility ,
406418 isRedacted = isClassRedacted,
407419 isParcelable = isParcelable,
408420 superClass = superclass,
@@ -417,8 +429,14 @@ public class AutoValueKotlinExtension : AutoValueExtension() {
417429 remainingMethods = remainingMethods,
418430 classAnnotations = avClass.classAnnotations(),
419431 redactedClassName = redactedClassName,
420- staticConstants = staticConstants
421- ).writeTo(srcDir, context.processingEnvironment().messager)
432+ staticConstants = staticConstants,
433+ isTopLevel = isTopLevel,
434+ children = nestedAvClasses
435+ .mapTo(LinkedHashSet ()) { it.asClassName() }
436+ .plus(collectedEnums.keys)
437+ )
438+
439+ collectedKclassees[context.autoValueClass().asClassName()] = kClass
422440
423441 return null
424442 }
@@ -464,11 +482,7 @@ private fun AvkBuilder.Companion.from(
464482 return AvkBuilder (
465483 name = builderContext.builderType().simpleName.toString(),
466484 doc = builderContext.builderType().parseDocs(),
467- visibility = if (Modifier .PUBLIC in builderContext.builderType().modifiers) {
468- KModifier .PUBLIC
469- } else {
470- KModifier .INTERNAL
471- },
485+ visibility = builderContext.builderType().visibility,
472486 builderProps = props,
473487 buildFun = builderContext.buildMethod()
474488 .map {
0 commit comments