Skip to content

Commit

Permalink
Add support for js annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
DRSchlaubi committed Sep 20, 2023
1 parent b865c3f commit 7a061dc
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 2 deletions.
11 changes: 11 additions & 0 deletions kotlinpoet/annotations/src/commonMain/kotlin/Annotator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.kord.codegen.ksp.annotations

@Repeatable
@MustBeDocumented
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
@ProcessorAnnotation("dev.kord.codegen.generator.annotator")
public annotation class Annotator(
val fromPackage: String,
val ignore: Array<String> = []
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package dev.kord.codegen.kotlinpoet.js

import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import dev.kord.codegen.kotlinpoet.addAnnotation
import kotlin.String

/**
* Adds `JsName` to this [FunSpec]
*/
public fun FunSpec.Builder.jsName(name: String) {
addAnnotation(ClassName("kotlin.js", "JsName")) {
addMember("%S", name)
}
}

/**
* Adds `JsName` to this [TypeSpec]
*/
public fun TypeSpec.Builder.jsName(name: String) {
addAnnotation(ClassName("kotlin.js", "JsName")) {
addMember("%S", name)
}
}

/**
* Adds `JsName` to this [PropertySpec]
*/
public fun PropertySpec.Builder.jsName(name: String) {
addAnnotation(ClassName("kotlin.js", "JsName")) {
addMember("%S", name)
}
}

/**
* Adds `JsExport` to this [FileSpec]
*/
public fun FileSpec.Builder.jsExport() {
addAnnotation(ClassName("kotlin.js", "JsExport"))
}

/**
* Adds `JsExport` to this [FunSpec]
*/
public fun FunSpec.Builder.jsExport() {
addAnnotation(ClassName("kotlin.js", "JsExport"))
}

/**
* Adds `JsExport` to this [TypeSpec]
*/
public fun TypeSpec.Builder.jsExport() {
addAnnotation(ClassName("kotlin.js", "JsExport"))
}

/**
* Adds `JsExport` to this [PropertySpec]
*/
public fun PropertySpec.Builder.jsExport() {
addAnnotation(ClassName("kotlin.js", "JsExport"))
}

/**
* Adds `Ignore` to this [FunSpec]
*/
public fun FunSpec.Builder.ignore() {
addAnnotation(ClassName("kotlin.js", "JsExport", "Ignore"))
}

/**
* Adds `Ignore` to this [TypeSpec]
*/
public fun TypeSpec.Builder.ignore() {
addAnnotation(ClassName("kotlin.js", "JsExport", "Ignore"))
}

/**
* Adds `Ignore` to this [PropertySpec]
*/
public fun PropertySpec.Builder.ignore() {
addAnnotation(ClassName("kotlin.js", "JsExport", "Ignore"))
}
33 changes: 33 additions & 0 deletions kotlinpoet/processor/src/main/kotlin/AnnotatorProcessor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.kord.codegen.generator

import com.google.devtools.ksp.containingFile
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.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile
import dev.kord.codegen.generator.annotator.getAnnotators
import dev.kord.codegen.generator.annotator.processAnnotators
import dev.kord.codegen.ksp.annotations.Annotator
import dev.kord.codegen.ksp.annotations.InlineConstructor
import dev.kord.codegen.ksp.getSymbolsWithAnnotation

/**
* Please refer to the documentation of [InlineConstructor].
*/
class AnnotatorProcessor private constructor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation<Annotator>()
.forEach {
val annotators = it.getAnnotators()
environment.processAnnotators(resolver, it as KSFile, annotators)
}

return emptyList()
}

class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = AnnotatorProcessor(environment)
}
}
90 changes: 90 additions & 0 deletions kotlinpoet/processor/src/main/kotlin/annotator/Annotator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
@file:JvmName("AnnotatorGenerator")

package dev.kord.codegen.generator.annotator

import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import dev.kord.codegen.generator.utils.ADD_ANNOTATION
import dev.kord.codegen.generator.utils.mapToValueParameterList
import dev.kord.codegen.kotlinpoet.CodeBlock
import dev.kord.codegen.kotlinpoet.FileSpec
import dev.kord.codegen.kotlinpoet.addFunction
import dev.kord.codegen.kotlinpoet.withControlFlow
import dev.kord.codegen.ksp.annotations.AnnotationArguments.Companion.arguments
import dev.kord.codegen.ksp.getAnnotationByType

@OptIn(KspExperimental::class)
fun SymbolProcessorEnvironment.processAnnotators(resolver: Resolver, origin: KSFile, annotators: Sequence<Annotator>) {
val fileSpec = FileSpec(origin.packageName.asString(), origin.fileName.dropLast(3)) {
annotators.forEach { annotator ->
resolver.getDeclarationsFromPackage(annotator.fromPackage)
.filterIsInstance<KSClassDeclaration>()
.filter { it.classKind == ClassKind.ANNOTATION_CLASS }
.filter { it.simpleName.asString() !in annotator.ignore }
.forEach {
val targetAnnotation = it.getAnnotationByType<Target>()
val targetsRaw = targetAnnotation.arguments<Target>()
val targets = targetsRaw[Target::allowedTargets]!!
it.generateAnnotator(origin, this)
}
}
}

fileSpec.writeTo(codeGenerator, false)
}

private fun KSClassDeclaration.generateAnnotator(origin: KSFile, builder: FileSpec.Builder) {
receiversByTarget.forEach {
builder.addFunction(simpleName.asString().replaceFirstChar { it.lowercase() }) {
addKdoc("Adds `%N` to this [%T]", toClassName().simpleName, it.enclosingClassName()!!)
addOriginatingKSFile(origin)
receiver(it)
primaryConstructor!!.parameters.forEach {
addParameter(it.name!!.asString(), it.type.toTypeName())
}

val type = toClassName()

if (parameters.isEmpty()) {
addCode(
"%M(%L)",
ADD_ANNOTATION,
type.toLiteral()
)
} else {
withControlFlow(
"%M(%L)", ADD_ANNOTATION,
type.toLiteral()
) {
val placeHolders = parameters.map {
if (it.type == STRING) {
CodeBlock.of("%%S")
} else {
CodeBlock.of("%%L")
}
}.joinToCode(", ")

addStatement("addMember(%S, %L)", placeHolders, parameters.mapToValueParameterList())
}
}
}
}
}

private fun ClassName.toLiteral() = CodeBlock {
add("%T(", ClassName::class.asClassName())
val parameters = (listOf(packageName) + simpleNames).map {
CodeBlock.of("%S", it)
}.joinToCode(", ")
add("%L", parameters)
add(")")
}
2 changes: 2 additions & 0 deletions kotlinpoet/processor/src/main/kotlin/utils/Names.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package dev.kord.codegen.generator.utils

import com.squareup.kotlinpoet.DelicateKotlinPoetApi
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.asClassName
import dev.kord.codegen.kotlinpoet.CodeGenDsl
import dev.kord.codegen.kotlinpoet.CodeGenInternal
Expand All @@ -18,4 +19,5 @@ private val produceByName: KFunction<SubSpecDelegateProvider<String>> = ::produc
val CODEGEN_DSL = CodeGenDsl::class.asClassName()
val EMPTY_CODE_BLOCK = ::emptyCodeBlock.asMemberName()
val PRODUCE_BY_NAME = produceByName.asMemberName()
val ADD_ANNOTATION = MemberName("dev.kord.codegen.kotlinpoet", "addAnnotation")
val SUB_SPEC_DELEGATE_PROVIDER = SubSpecDelegateProvider::class.asClassName()
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dev.kord.codegen.generator.CodeProcessor$Provider
dev.kord.codegen.generator.ConstructorInliner$Provider
dev.kord.codegen.generator.AnnotatorProcessor$Provider
8 changes: 8 additions & 0 deletions kotlinpoet/src/commonMain/kotlin/js/JsAnnotations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@file:Annotator(
"kotlin.js",
ignore = ["ExperimentalJsExport"]
)

package dev.kord.codegen.kotlinpoet.js

import dev.kord.codegen.ksp.annotations.Annotator
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fun KSType.isMappedAnnotation(rootType: KSClassDeclaration): Boolean {
val declaration = declaration
return if (declaration is KSClassDeclaration && declaration.classKind == ClassKind.ANNOTATION_CLASS) {
declaration.parentDeclaration == rootType
|| declaration.annotations
|| declaration.annotations
.any { it.annotationType.resolve().declaration.qualifiedName!!.asString() == PROCESSOR_ANNOTATION }
} else {
false
Expand All @@ -39,7 +39,9 @@ private fun KSTypeReference.dataClassType(rootType: KSClassDeclaration): TypeNam
else -> {
val declaration = resolvedType.declaration
if (declaration is KSClassDeclaration && declaration.classKind == ClassKind.ANNOTATION_CLASS) {
if (resolvedType.isMappedAnnotation(rootType)) {
if (resolvedType.isMappedAnnotation(rootType)
|| (resolvedType.declaration as? KSClassDeclaration)?.classKind == ClassKind.ENUM_CLASS
) {
ClassName("", declaration.simpleName.asString())
} else {
KSAnnotation::class.asClassName()
Expand Down

0 comments on commit 7a061dc

Please sign in to comment.