Add @Subscribe annotation

[no changelog]
This commit is contained in:
Linnea Gräf
2024-05-07 20:26:04 +02:00
parent 0cb976ce07
commit 8f3cc34740
11 changed files with 252 additions and 47 deletions

19
symbols/build.gradle.kts Normal file
View File

@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
plugins {
kotlin("jvm")
id("com.google.devtools.ksp")
}
repositories {
mavenCentral()
}
dependencies {
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
implementation("com.google.auto.service:auto-service-annotations:1.1.1")
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20")
}

View File

@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.annotations
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class Subscribe

View File

@@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.annotations.process
import com.google.auto.service.AutoService
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
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.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
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.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.Nullability
import com.google.devtools.ksp.validate
import java.text.SimpleDateFormat
import java.util.Date
import moe.nea.firmament.annotations.Subscribe
class SubscribeAnnotationProcessor(
val logger: KSPLogger,
val codeGenerator: CodeGenerator,
) : SymbolProcessor {
override fun finish() {
val dependencies = Dependencies(
aggregating = true,
*subscriptions.mapTo(mutableSetOf()) { it.parent.containingFile!! }.toTypedArray())
val subscriptionsFile =
codeGenerator
.createNewFile(dependencies, "moe.nea.firmament.annotations.generated", "AllSubscriptions")
.bufferedWriter()
subscriptionsFile.apply {
appendLine("// This file is @generated by SubscribeAnnotationProcessor at ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(
Date())}")
appendLine("// Do not edit")
appendLine("package moe.nea.firmament.annotations.generated")
appendLine()
appendLine("import moe.nea.firmament.events.subscription.*")
appendLine()
appendLine("object AllSubscriptions {")
appendLine(" fun provideSubscriptions(addSubscription: (Subscription<*>) -> Unit) {")
for (subscription in subscriptions) {
val owner = subscription.parent.qualifiedName!!.asString()
val method = subscription.child.simpleName.asString()
val type = subscription.type.declaration.qualifiedName!!.asString()
appendLine(" addSubscription(Subscription<$type>(")
appendLine(" ${owner},")
appendLine(" ${owner}::${method},")
appendLine(" ${type}))")
}
appendLine(" }")
appendLine("}")
}
subscriptionsFile.close()
}
data class Subscription(
val parent: KSClassDeclaration,
val child: KSFunctionDeclaration,
val type: KSType,
)
val subscriptions = mutableListOf<Subscription>()
fun processCandidates(list: List<KSAnnotated>) {
for (element in list) {
if (element !is KSFunctionDeclaration) {
logger.error("@Subscribe annotation on a not-function", element)
continue
}
if (element.isAbstract) {
logger.error("@Subscribe annotation on an abstract function", element)
continue
}
val parent = element.parentDeclaration
if (parent !is KSClassDeclaration || parent.isCompanionObject || parent.classKind != ClassKind.OBJECT) {
logger.error("@Subscribe on a non-object", element)
continue
}
val param = element.parameters.singleOrNull()
if (param == null) {
logger.error("@Subscribe annotated functions need to take exactly one parameter", element)
continue
}
val type = param.type.resolve()
if (type.nullability != Nullability.NOT_NULL) {
logger.error("@Subscribe annotated functions cannot take a nullable event", element)
continue
}
subscriptions.add(Subscription(parent, element, type))
}
}
override fun process(resolver: Resolver): List<KSAnnotated> {
val candidates = resolver.getSymbolsWithAnnotation(Subscribe::class.qualifiedName!!).toList()
val valid = candidates.filter { it.validate() }
val invalid = candidates.filter { !it.validate() }
processCandidates(valid)
return invalid
}
}
@AutoService(SymbolProcessorProvider::class)
class SubscribeAnnotationProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return SubscribeAnnotationProcessor(environment.logger, environment.codeGenerator)
}
}