From 8f3cc34740fcfe1572d23c8f1c1db1a309217b84 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Tue, 7 May 2024 20:26:04 +0200 Subject: Add @Subscribe annotation [no changelog] --- symbols/build.gradle.kts | 19 ++++ symbols/src/main/kotlin/Subscribe.kt | 12 +++ .../kotlin/process/SubscribeAnnotationProcessor.kt | 116 +++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 symbols/build.gradle.kts create mode 100644 symbols/src/main/kotlin/Subscribe.kt create mode 100644 symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt (limited to 'symbols') diff --git a/symbols/build.gradle.kts b/symbols/build.gradle.kts new file mode 100644 index 0000000..fad3221 --- /dev/null +++ b/symbols/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * 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") +} diff --git a/symbols/src/main/kotlin/Subscribe.kt b/symbols/src/main/kotlin/Subscribe.kt new file mode 100644 index 0000000..e7a7bf4 --- /dev/null +++ b/symbols/src/main/kotlin/Subscribe.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.annotations + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +annotation class Subscribe + diff --git a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt new file mode 100644 index 0000000..be817ce --- /dev/null +++ b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * 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() + + fun processCandidates(list: List) { + 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 { + 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) + } +} -- cgit