Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prepare 4.0 with ksp. closes #6 #7

Merged
merged 1 commit into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ allprojects {
apply plugin: 'maven-publish'

group 'co.selim.goldfinch'
version '3.0.1'
version '4.0.0'

java {
withSourcesJar()
Expand Down
2 changes: 1 addition & 1 deletion codegen/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ repositories {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation "com.google.devtools.ksp:symbol-processing-api:1.7.10-1.0.6"
implementation "com.squareup:kotlinpoet:1.12.0"
implementation "com.squareup:kotlinpoet-metadata:1.12.0"
implementation project(':annotation')
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package co.selim.goldfinch.codegen

import co.selim.goldfinch.annotation.GenerateProperties
import co.selim.goldfinch.annotation.Level
import co.selim.goldfinch.annotation.Visibility
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.getVisibility
import com.google.devtools.ksp.processing.*
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.KSType
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName

class GoldfinchSymbolProcessor(private val logger: KSPLogger, private val codeGenerator: CodeGenerator) : SymbolProcessor {

private fun ClassName.safelyParameterizedBy(typeNames: List<TypeName>?): TypeName {
return if (typeNames.isNullOrEmpty()) {
this
} else {
this.parameterizedBy(typeNames)
}
}

private fun logError(msg: String, node: KSNode? = null): Nothing {
logger.error(msg, node)
error(msg)
}

private fun generatePropertyExtension(
receiver: ClassName,
properties: Map<String, TypeName>,
visibilityModifier: KModifier,
nestProperties: Boolean,
): FileSpec {
val sealedInterfaceName = ClassName(receiver.packageName, "${receiver.simpleName}Property")
val sealedInterface = generateSealedInterface(sealedInterfaceName, visibilityModifier)
val propertySpecs = properties.map { (propertyName, propertyType) ->
generatePropertyContainer(sealedInterfaceName, propertyName, propertyType, visibilityModifier, nestProperties)
}
return generateFile(receiver)
.apply {
if (nestProperties)
addType(sealedInterface.addTypes(propertySpecs).build())
else {
addType(sealedInterface.build())
propertySpecs.forEach(::addType)
}
}
.addProperty(
generatePropertyMapper(
sealedInterfaceName,
receiver,
properties,
visibilityModifier,
nestProperties
)
)
.build()
}

private fun KSType.getFullType(): TypeName {
val typeParams = arguments.mapNotNull { argument ->
val resolvedType = argument.type?.resolve()

resolvedType?.getFullType()?.copy(nullable = resolvedType.isMarkedNullable)
}

val fullName = declaration.qualifiedName!!.asString()
val simpleName = fullName.substringAfter(declaration.packageName.asString())
return ClassName(declaration.packageName.asString(), simpleName)
.safelyParameterizedBy(typeParams)
.copy(nullable = isMarkedNullable)
}

@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation(GenerateProperties::class.java.name)
.filterIsInstance(KSClassDeclaration::class.java)
.forEach { ksClassDeclaration ->
val annotatedClass = ksClassDeclaration.qualifiedName!!.asString()
val properties = ksClassDeclaration.getAllProperties()
.associate { ksPropertyDeclaration ->
val propertyName = ksPropertyDeclaration.simpleName.getShortName()
propertyName to ksPropertyDeclaration.type.resolve().getFullType()
}

val annotation = ksClassDeclaration.getAnnotationsByType(GenerateProperties::class).first()
val visibilityModifier = annotation.visibility.toKModifier(ksClassDeclaration)
val dependencies = Dependencies(true, ksClassDeclaration.containingFile!!)

codeGenerator.createNewFile(
dependencies,
ksClassDeclaration.packageName.asString(),
"${annotatedClass}Properties"
)
.bufferedWriter()
.use { writer ->
generatePropertyExtension(
ClassName.bestGuess(annotatedClass),
properties,
visibilityModifier,
annotation.level == Level.NESTED
).writeTo(writer)
}
}

return emptyList()
}


private fun Visibility.toKModifier(classDeclaration: KSClassDeclaration): KModifier {
return when (this) {
Visibility.PUBLIC -> KModifier.PUBLIC
Visibility.INTERNAL -> KModifier.INTERNAL
Visibility.INHERIT -> classDeclaration.getVisibilityKModifier()
}
}

private fun KSClassDeclaration.getVisibilityKModifier(): KModifier {
return when (val visibility = this.getVisibility()) {
com.google.devtools.ksp.symbol.Visibility.PUBLIC -> KModifier.PUBLIC
com.google.devtools.ksp.symbol.Visibility.PROTECTED -> KModifier.PROTECTED
com.google.devtools.ksp.symbol.Visibility.INTERNAL -> KModifier.INTERNAL
com.google.devtools.ksp.symbol.Visibility.PRIVATE,
com.google.devtools.ksp.symbol.Visibility.LOCAL,
com.google.devtools.ksp.symbol.Visibility.JAVA_PACKAGE -> {
val msg = "Visibility '$visibility' on class '${this.qualifiedName!!.asString()}' is not supported"
logError(msg, this)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.selim.goldfinch.codegen

import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider

class GoldfinchSymbolProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return GoldfinchSymbolProcessor(environment.logger, environment.codeGenerator)
}
}
171 changes: 0 additions & 171 deletions codegen/src/main/kotlin/co/selim/goldfinch/codegen/ServiceProcessor.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
co.selim.goldfinch.codegen.GoldfinchSymbolProcessorProvider

This file was deleted.

17 changes: 15 additions & 2 deletions example/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'org.jetbrains.kotlin.kapt'
id 'com.google.devtools.ksp' version '1.7.10-1.0.6'
}

repositories {
Expand All @@ -10,5 +10,18 @@ repositories {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation project(':annotation')
kapt project(':codegen')
ksp project(':codegen')
}

sourceSets {
main {
java {
srcDir(file("build/generated/ksp/main/kotlin"))
}
}
test {
java {
srcDir(file("build/generated/ksp/test/kotlin"))
}
}
}
5 changes: 2 additions & 3 deletions example/src/main/kotlin/co/selim/goldfinch/example/Sample.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ internal data class Person(
)

@GenerateProperties
internal data class Animal(
val name: String
)
internal data class Animal(val name: String)

fun main() {

val selim = Person("Selim Dinçer", LocalDate.of(1970, 1, 1))

selim.properties
Expand Down