diff --git a/README.md b/README.md index f2696ee..512cc76 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ And you will have to include the required dependencies: ```groovy dependencies { - implementation 'com.github.tobrun.kotlin-data-compat:annotation:0.4.0' - ksp 'com.github.tobrun.kotlin-data-compat:processor:0.4.0' + implementation 'com.github.tobrun.kotlin-data-compat:annotation:0.4.1' + ksp 'com.github.tobrun.kotlin-data-compat:processor:0.4.1' } ``` diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a9715..774fae8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/processor/src/main/kotlin/com/tobrun/datacompat/DataCompatProcessor.kt b/processor/src/main/kotlin/com/tobrun/datacompat/DataCompatProcessor.kt index 65ec6d2..66de94f 100644 --- a/processor/src/main/kotlin/com/tobrun/datacompat/DataCompatProcessor.kt +++ b/processor/src/main/kotlin/com/tobrun/datacompat/DataCompatProcessor.kt @@ -8,7 +8,9 @@ import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.symbol.ClassKind import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSNode import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.KSValueParameter import com.google.devtools.ksp.symbol.KSVisitorVoid import com.google.devtools.ksp.symbol.Modifier import com.google.devtools.ksp.validate @@ -45,19 +47,63 @@ class DataCompatProcessor( override fun process(resolver: Resolver): List { logger.info("DataCompat: process") - val annotated = resolver.getSymbolsWithAnnotation(DataCompat::class.qualifiedName!!, true) - if (annotated.count() == 0) { + val dataCompatAnnotated = resolver.getSymbolsWithAnnotation(DataCompat::class.qualifiedName!!, true) + if (dataCompatAnnotated.count() == 0) { logger.info("DataCompat: No DataCompat annotations found for processing") return emptyList() } - val unableToProcess = annotated.filterNot { it.validate() } - annotated.filter { it is KSClassDeclaration && it.validate() } - .forEach { it.accept(Visitor(), Unit) } + // symbols returned by + // resolver.getSymbolsWithAnnotation(DataCompat::class.qualifiedName!!, true) + // don't expose their annotations. + // Therefore we can't access the @Default annotation required to build the code + // for default values. + val classToDefaultValuesMap = + mutableMapOf>() + val imports = ArrayList() + val symbolsWithDefaultAnnotation = + resolver.getSymbolsWithAnnotation(Default::class.qualifiedName!!, true) + symbolsWithDefaultAnnotation.forEach { annotatedProperty -> + // since KSP 1.8.10-1.0.9 annotatedProperty are returned ONLY as KSValueParameter; + // for previous KSP versions they were also returned as KSPropertyDeclaration + if (annotatedProperty is KSValueParameter) { + val parentClass = annotatedProperty.findParentClass()!! + var defaultValueMap = classToDefaultValuesMap[parentClass] + if (defaultValueMap == null) { + defaultValueMap = mutableMapOf() + } + val defaultAnnotationsParams = annotatedProperty.annotations.firstOrNull()?.arguments + val defaultValue = defaultAnnotationsParams?.first() + defaultValueMap[annotatedProperty.name!!.getShortName()] = + defaultValue?.value as? String? + defaultAnnotationsParams?.getOrNull(1)?.value?.let { + imports.addAll(it as ArrayList) + } + classToDefaultValuesMap[parentClass] = defaultValueMap + } + } + + val unableToProcess = dataCompatAnnotated.filterNot { it.validate() } + dataCompatAnnotated.filter { it is KSClassDeclaration && it.validate() } + .forEach { it.accept(Visitor(classToDefaultValuesMap, imports), Unit) } return unableToProcess.toList() } - private inner class Visitor : KSVisitorVoid() { + private fun KSNode.findParentClass(): KSClassDeclaration? { + var currentParent: KSNode? = parent + while (currentParent !is KSClassDeclaration) { + currentParent = parent?.parent + if (currentParent == null) { + return null + } + } + return currentParent + } + + private inner class Visitor( + private val defaultValuesMap: Map>, + private val imports: List + ) : KSVisitorVoid() { @Suppress("LongMethod", "MaxLineLength", "ComplexMethod") override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { @@ -77,7 +123,6 @@ class DataCompatProcessor( val implementedInterfaces = classDeclaration .superTypes .filter { (it.resolve().declaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE } - val imports = ArrayList() // Map KSP properties with KoltinPoet TypeNames val propertyMap = mutableMapOf() @@ -220,19 +265,13 @@ class DataCompatProcessor( val builderBuilder = TypeSpec.classBuilder("Builder") for (property in propertyMap) { val propertyName = property.key.toString() - val defaultAnnotationsParams = property.key.annotations - .firstOrNull { it.annotationType.resolve().toString() == Default::class.simpleName } - ?.arguments - val defaultValue = defaultAnnotationsParams?.first() - defaultAnnotationsParams?.getOrNull(1)?.value?.let { - imports.addAll(it as ArrayList) - } + val nullableType = property.value.copy(nullable = true) builderBuilder.addProperty( PropertySpec.builder(propertyName, nullableType) .initializer( CodeBlock.builder() - .add((defaultValue?.value as? String?) ?: "null") + .add(defaultValuesMap[classDeclaration]?.get(propertyName) ?: "null") .build() ) .addAnnotation(