Skip to content
Snippets Groups Projects
Verified Commit 7b55d0b5 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Cleanup annotation/symbol processing

parent 369f3358
No related branches found
No related tags found
No related merge requests found
Showing
with 478 additions and 133 deletions
......@@ -10,28 +10,16 @@
package de.justjanne.libquassel.generator
import com.google.devtools.ksp.getDeclaredFunctions
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSValueParameter
import com.google.devtools.ksp.symbol.KSVisitorVoid
import de.justjanne.libquassel.annotations.ProtocolSide
import de.justjanne.libquassel.annotations.SyncedCall
import de.justjanne.libquassel.annotations.SyncedObject
import de.justjanne.libquassel.generator.annotation.SyncedCallAnnotationModel
import de.justjanne.libquassel.generator.annotation.SyncedObjectAnnotationModel
import de.justjanne.libquassel.generator.model.SyncedCallModel
import de.justjanne.libquassel.generator.model.SyncedObjectModel
import de.justjanne.libquassel.generator.model.SyncedParameterModel
import de.justjanne.libquassel.generator.util.findAnnotationWithType
import de.justjanne.libquassel.generator.util.getMember
import de.justjanne.libquassel.generator.util.toEnum
import de.justjanne.libquassel.generator.visitors.KSDeclarationParser
import de.justjanne.libquassel.generator.visitors.KotlinSaver
import de.justjanne.libquassel.generator.visitors.RpcModelProcessor
class InvokerProcessor : SymbolProcessor {
lateinit var codeGenerator: CodeGenerator
......@@ -48,111 +36,18 @@ class InvokerProcessor : SymbolProcessor {
}
override fun process(resolver: Resolver): List<KSAnnotated> {
val classes = resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
.mapNotNull { it.accept(KSDeclarationParser(resolver, logger), Unit) }
.flatMap {
val visitor = QuasselRpcVisitor(resolver)
it.accept(visitor, Unit)
visitor.classes
}
val message = classes.flatMap {
listOf(
"@SyncedObject(name=${it.rpcName})",
it.name,
) + it.methods.flatMap {
listOf(
"@SyncedCall(name=${it.rpcName}, target=${it.side})",
it.name,
) + it.parameters.flatMap {
listOf(
"${it.name}: ${it.type}"
listOfNotNull(
it.accept(RpcModelProcessor(), ProtocolSide.CLIENT),
it.accept(RpcModelProcessor(), ProtocolSide.CORE),
)
}.map { " $it" }
}.map { " $it" }
}.joinToString("\n")
logger.logging("Data: \n\n$message\n\n")
return emptyList()
}
override fun finish() {
// TODO
}
inner class QuasselRpcVisitor(
private val resolver: Resolver
) : KSVisitorVoid() {
val classes = mutableListOf<SyncedObjectModel>()
var functions = mutableListOf<SyncedCallModel>()
var parameters = mutableListOf<SyncedParameterModel>()
private fun collectFunctions(): List<SyncedCallModel> {
val result = functions
functions = mutableListOf()
return result
}
private fun collectParameters(): List<SyncedParameterModel> {
val result = parameters
parameters = mutableListOf()
return result
}
.map { it.accept(KotlinSaver(), codeGenerator) }
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
try {
val annotation = classDeclaration.findAnnotationWithType<SyncedObject>(resolver) ?: return
val syncedObjectModel = SyncedObjectAnnotationModel(
name = annotation.getMember("name"),
)
classDeclaration.getDeclaredFunctions().map { it.accept(this, Unit) }
classes.add(
SyncedObjectModel(
classDeclaration.qualifiedName?.asString() ?: return,
syncedObjectModel.name,
collectFunctions()
)
)
} catch (t: Throwable) {
logger.error("Error processing class ${classDeclaration.qualifiedName?.asString()}", classDeclaration)
logger.exception(t)
}
}
override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
val annotation = function.findAnnotationWithType<SyncedCall>(resolver) ?: return
try {
val syncedCallModel = SyncedCallAnnotationModel(
name = annotation.getMember("name"),
target = annotation.getMember<KSType>("target")?.toEnum<ProtocolSide>(),
)
function.parameters.map { it.accept(this, Unit) }
functions.add(
SyncedCallModel(
function.simpleName.asString(),
syncedCallModel.name,
syncedCallModel.target,
collectParameters()
)
)
} catch (t: Throwable) {
logger.error("Error processing function ${function.qualifiedName?.asString()}", function)
logger.exception(t)
}
return emptyList()
}
override fun visitValueParameter(valueParameter: KSValueParameter, data: Unit) {
try {
super.visitValueParameter(valueParameter, data)
parameters.add(
SyncedParameterModel(
valueParameter.name?.asString(),
valueParameter.type.resolve()
)
)
} catch (t: Throwable) {
logger.error("Error processing parameter ${valueParameter.name?.asString()}", valueParameter)
logger.exception(t)
}
}
}
override fun finish() = Unit
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.annotation
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSType
import de.justjanne.libquassel.annotations.ProtocolSide
import de.justjanne.libquassel.annotations.SyncedCall
import de.justjanne.libquassel.generator.util.findAnnotationWithType
import de.justjanne.libquassel.generator.util.getMember
import de.justjanne.libquassel.generator.util.toEnum
data class RpcFunctionAnnotation(
val name: String?,
val target: ProtocolSide?
) {
companion object {
fun of(it: KSAnnotated, resolver: Resolver): RpcFunctionAnnotation? {
val annotation = it.findAnnotationWithType<SyncedCall>(resolver)
?: return null
return RpcFunctionAnnotation(
name = annotation.getMember("name"),
target = annotation.getMember<KSType>("target")
?.toEnum<ProtocolSide>(),
)
}
}
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.annotation
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import de.justjanne.libquassel.annotations.SyncedObject
import de.justjanne.libquassel.generator.util.findAnnotationWithType
import de.justjanne.libquassel.generator.util.getMember
data class RpcObjectAnnotation(
val name: String?
) {
companion object {
fun of(it: KSAnnotated, resolver: Resolver): RpcObjectAnnotation? {
val annotation = it.findAnnotationWithType<SyncedObject>(resolver)
?: return null
return RpcObjectAnnotation(
name = annotation.getMember("name"),
)
}
}
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.kotlinmodel
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
sealed class KotlinModel {
data class FileModel(
val source: KSClassDeclaration,
val data: FileSpec
) : KotlinModel() {
override fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D) =
visitor.visitFileModel(this, data)
}
data class FunctionModel(
val source: KSFunctionDeclaration,
val data: CodeBlock
) : KotlinModel() {
override fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D) =
visitor.visitFunctionModel(this, data)
}
abstract fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D): R
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.kotlinmodel
interface KotlinModelVisitor<D, R> {
fun visitFileModel(model: KotlinModel.FileModel, data: D): R
fun visitFunctionModel(model: KotlinModel.FunctionModel, data: D): R
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.rpcmodel
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSValueParameter
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.TypeName
import de.justjanne.libquassel.annotations.ProtocolSide
sealed class RpcModel {
data class ObjectModel(
val source: KSClassDeclaration,
val name: ClassName,
val rpcName: String?,
val methods: List<FunctionModel>
) : RpcModel() {
override fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D) =
visitor.visitObjectModel(this, data)
}
data class FunctionModel(
val source: KSFunctionDeclaration,
val name: String,
val rpcName: String?,
val side: ProtocolSide?,
val parameters: List<ParameterModel>
) : RpcModel() {
override fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D) =
visitor.visitFunctionModel(this, data)
}
data class ParameterModel(
val source: KSValueParameter,
val name: String?,
val type: TypeName
) : RpcModel() {
override fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D) =
visitor.visitParameterModel(this, data)
}
abstract fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D): R
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.rpcmodel
interface RpcModelVisitor<D, R> {
fun visitObjectModel(model: RpcModel.ObjectModel, data: D): R
fun visitFunctionModel(model: RpcModel.FunctionModel, data: D): R
fun visitParameterModel(model: RpcModel.ParameterModel, data: D): R
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.util.kotlinpoet
class ArgString constructor(
val name: String,
vararg val args: Any?
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ArgString
if (name != other.name) return false
if (!args.contentEquals(other.args)) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + args.contentHashCode()
return result
}
override fun toString(): String {
return "ArgString(name='$name', args=${args.contentToString()})"
}
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.util.kotlinpoet
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.buildCodeBlock
class WhenBlockBuilder constructor(
private val over: ArgString
) {
private val cases = mutableListOf<Pair<ArgString, CodeBlock>>()
constructor(name: String, vararg args: Any?) : this(ArgString(name, args))
fun addCase(condition: ArgString, block: CodeBlock) {
cases.add(Pair(condition, block))
}
fun build(): CodeBlock = buildCodeBlock {
beginControlFlow("when (${over.name})", over.args)
for ((condition, code) in cases) {
beginControlFlow("${condition.name} ->", *condition.args)
add(code)
endControlFlow()
}
endControlFlow()
}
inline fun addCase(name: String, vararg args: Any?, f: CodeBlock.Builder.() -> Unit) {
addCase(ArgString(name, args), buildCodeBlock(f))
}
inline fun buildElse(f: CodeBlock.Builder.() -> Unit) {
addCase(ArgString("else"), buildCodeBlock(f))
}
}
......@@ -8,13 +8,10 @@
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.model
package de.justjanne.libquassel.generator.util.kotlinpoet
import de.justjanne.libquassel.annotations.ProtocolSide
import com.squareup.kotlinpoet.CodeBlock
data class SyncedCallModel(
val name: String,
val rpcName: String?,
val side: ProtocolSide?,
val parameters: List<SyncedParameterModel>
)
inline fun CodeBlock.Builder.buildWhen(name: String, vararg args: Any?, f: WhenBlockBuilder.() -> Unit) {
this.add(WhenBlockBuilder(name, args).apply(f).build())
}
......@@ -8,11 +8,12 @@
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.annotation
package de.justjanne.libquassel.generator.util.kotlinpoet
import de.justjanne.libquassel.annotations.ProtocolSide
import com.squareup.kotlinpoet.CodeBlock
data class SyncedCallAnnotationModel(
val name: String?,
val target: ProtocolSide?
)
fun CodeBlock.Builder.withIndent(f: CodeBlock.Builder.() -> Unit) {
indent()
f()
unindent()
}
......@@ -10,33 +10,25 @@
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.isLocal
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.squareup.kotlinpoet.ClassName
internal fun KSType.toClassName(): ClassName {
val decl = declaration
check(decl is KSClassDeclaration)
return decl.toClassName()
private fun KSDeclaration.parents(): List<KSDeclaration> {
val declarations = mutableListOf(this)
var parent = this.parentDeclaration
while (parent != null) {
declarations.add(parent)
parent = parent.parentDeclaration
}
internal fun KSClassDeclaration.toClassName(): ClassName {
require(!isLocal()) {
"Local/anonymous classes are not supported!"
return declarations.reversed()
}
val pkgName = packageName.asString()
val typesString = qualifiedName!!.asString().removePrefix("$pkgName.")
val simpleNames = typesString
.split(".")
return ClassName(pkgName, simpleNames)
fun KSDeclaration.asClassName(): ClassName {
return ClassName(
packageName.asString(),
parents().map { it.simpleName.asString() }
)
}
fun Class<*>.toClassName(): ClassName {
val packageName = `package`.name
val names = canonicalName.substring(packageName.length)
.trimStart('.')
.split('.')
return ClassName(packageName, names)
}
fun KSType.asClassName(): ClassName = declaration.asClassName()
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.Variance
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.WildcardTypeName
fun KSDeclaration.asTypeName(): TypeName =
ClassName(packageName.asString(), simpleName.asString())
fun KSType.asTypeName(): TypeName {
val baseType = asClassName()
if (arguments.isEmpty()) {
return baseType
}
val parameters = arguments.map {
val type = it.type?.resolve()
when (it.variance) {
Variance.STAR ->
WildcardTypeName.producerOf(Any::class)
.copy(nullable = true)
Variance.INVARIANT ->
type!!.asTypeName()
.copy(nullable = type.isMarkedNullable)
Variance.COVARIANT ->
WildcardTypeName.producerOf(type!!.asTypeName())
.copy(nullable = type.isMarkedNullable)
Variance.CONTRAVARIANT ->
WildcardTypeName.consumerOf(type!!.asTypeName())
.copy(nullable = type.isMarkedNullable)
}
}
return baseType.parameterizedBy(parameters)
}
......@@ -30,7 +30,7 @@ internal inline fun <reified T> KSAnnotation.getMember(name: String): T? {
is KSType ->
when {
T::class.java != ClassName::class.java -> null
else -> argValue.toClassName() as T
else -> argValue.asTypeName() as T
}
else -> null
}
......
......@@ -12,9 +12,10 @@ package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.symbol.KSType
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.asClassName
internal inline fun <reified T : Enum<T>> KSType.toEnum(): T? {
return toClassName().toEnum(T::class.java)
return asClassName().toEnum(T::class.java)
}
internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? {
......@@ -22,7 +23,7 @@ internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? {
}
internal fun <T : Enum<T>> ClassName.toEnum(clazz: Class<T>): T? {
val enumClassName = clazz.toClassName()
val enumClassName = clazz.asClassName()
return clazz.enumConstants.find {
this.canonicalName == enumClassName.nestedClass(it.name).canonicalName
}
......
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.visitors
import com.google.devtools.ksp.getDeclaredFunctions
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSNode
import com.google.devtools.ksp.symbol.KSValueParameter
import com.google.devtools.ksp.visitor.KSEmptyVisitor
import com.squareup.kotlinpoet.ClassName
import de.justjanne.libquassel.generator.annotation.RpcFunctionAnnotation
import de.justjanne.libquassel.generator.annotation.RpcObjectAnnotation
import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.util.asTypeName
class KSDeclarationParser(
private val resolver: Resolver,
private val logger: KSPLogger
) : KSEmptyVisitor<Unit, RpcModel?>() {
override fun visitClassDeclaration(
classDeclaration: KSClassDeclaration,
data: Unit
): RpcModel.ObjectModel? {
val annotation = RpcObjectAnnotation.of(classDeclaration, resolver)
?: return null
try {
return RpcModel.ObjectModel(
classDeclaration,
ClassName(
classDeclaration.packageName.asString(),
classDeclaration.simpleName.asString()
),
annotation.name,
classDeclaration.getDeclaredFunctions()
.mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.FunctionModel }
)
} catch (t: Throwable) {
logger.error("Error processing ${annotation.name}", classDeclaration)
logger.exception(t)
throw t
}
}
override fun visitFunctionDeclaration(
function: KSFunctionDeclaration,
data: Unit
): RpcModel.FunctionModel? {
val annotation = RpcFunctionAnnotation.of(function, resolver)
?: return null
try {
return RpcModel.FunctionModel(
function,
function.simpleName.asString(),
annotation.name,
annotation.target,
function.parameters
.mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.ParameterModel }
)
} catch (t: Throwable) {
logger.error("Error processing ${annotation.name ?: function.simpleName.asString()}", function)
logger.exception(t)
throw t
}
}
override fun visitValueParameter(
valueParameter: KSValueParameter,
data: Unit
): RpcModel.ParameterModel {
try {
return RpcModel.ParameterModel(
valueParameter,
valueParameter.name?.asString(),
valueParameter.type.resolve().asTypeName()
)
} catch (t: Throwable) {
logger.error("Error processing ${valueParameter.name?.asString()}", valueParameter)
logger.exception(t)
throw t
}
}
override fun defaultHandler(node: KSNode, data: Unit): RpcModel? = null
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment